java之线程方法(wait,join[插队方法],notify[唤醒线程],setDaemon[线程守护]),完整版的懒汉式单例,接口回调和同步代码块的弊端与好处

一.线程方法

.wait方法
线程使用wait()方法后,进入等待状态,然后会将锁还回去,其他的线程才能进来。
线程被唤醒后,线程是从等待的位置继续向下开始执行。
notify方法
notify() :在同一把锁下 ,等待中的线程中,随机唤醒一个。
notifyAll() :唤醒同一把锁下 所有的等待线程。
notify和wait方法应用:(wait方法需要声明标记控制等待线程被唤醒)

/*
需求:
* Person类 姓名 性别
* 开启两个线程
* 一个对Person对象进行赋值
* 一个对Person对象进行打印
* 要求
* 一次打印 东东 男
* 一次打印 dongdong nv
* 间隔输出
*/
public class Kll {
	public static void main(String[] args) {
		// 创建共享Person
		Person1 person = new Person1();
		// 创建赋值和打印线程
		CopyThread1 copyThread = new CopyThread1(person);
		Thread t1= new Thread(copyThread);
		
		PrintThread1 printThread = new PrintThread1(person);
		Thread t2 = new Thread(printThread);
		
		// 开启线程
		t1.start();
		t2.start();
	}
}
class Person1 {
	public String name;
	public String gander;
	// 声明标记,控制等待线程唤醒
	public boolean flag = true;
	
	// 封装同步代码块
	// 赋值
	public synchronized void setPerson(String name, String gander) {
		// 判断等待
		if (flag == false) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		// 赋值
		this.name = name;
		this.gander = gander;
		// 唤醒
		this.flag = false;
		this.notify();
	}
	// 打印
	public synchronized void printPerson() {
		if (flag == true) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(name + "--" + gander);
		// 唤醒
		this.flag = true;
		this.notify();
	}
}
// 赋值线程
// 注意:赋值和打印要使用同一个对象
// 只创建一个对象,把person对象,当做参数传入线程
// 避免赋值线程还没完成赋值,就被打印线程打印了的问题;需要给一个锁
// 使用什么锁合适?
// 保证同一把锁 直接使用person当锁对象 

// 如何实现间隔打印?
// 等赋值完成了,再进行打印  wait() 和 notify()
// 使用标记来控制等待和唤醒 保证两个线程使用的同一个标记
// 直接声明在Person类中
// 能不能封装成同步方法?写在哪儿?
// 能不能直接写在Person类中,同步成set/get方法

class CopyThread1 implements Runnable{
	// 声明标记,用于间隔赋值
	private boolean isTrue = false;
	// 将Person对象声明为一个成员变量
	private Person1 p;
	// 通过构造方法给p对象赋值
	public CopyThread1() {
	}
	public CopyThread1(Person1 p) {
		this.p = p;
	}
	
	@Override
	public void run() {
		
		while (true) {
			if (isTrue) {
				p.setPerson("东东", "男");
			}else {
				p.setPerson("dongdong", "nv");
			}
			isTrue = !isTrue;
		}	
	}
}
// 打印线程
class PrintThread1 implements Runnable{
	// 将Person对象声明为一个成员变量
		private Person1 p;
		// 通过构造方法给p对象赋值
		public PrintThread1() {
		}
		public PrintThread1(Person1 p) {
			this.p = p;
		}

	@Override
	public void run() {
		while (true) {
			p.printPerson();
			
		}
		
		
	}
	
}

join方法
插队方法:获取CPU的执行资源(先执行调用了join方法的线程)。
setDaemon方法
守护线程(当所有线程都结束了,这个线程就会结束)。
参数是:设置是否守护线程。
注意:要线程启动前调用
notify方法和setDaemon方法的应用代码:

需求:/*
 * 创建两个子线程
 * 第一个子线程循环打印10次名字
 * 第二个死循环打印线程名字
 * 主线程 循环10次名字
 * 要求
 * 等第一个子线程打印完 ,主线程再打印,主线程结束后,第二个子线程结束打印
 */


public class Kll {
	public static void main(String[] args) {
		// 守护线程(当所有线程都结束了,这个线程就会结束)
		TestARun testARun = new TestARun();
		Thread t2 = new Thread(testARun);
		// 设置是否守护线程
		// 注意:要线程启动前调用
		t2.setDaemon(true);
		t2.start();

		
		TestRun testRun = new TestRun();
		Thread t1 = new Thread(testRun);
		t1.start();
		
		// 插队方法
		// 获取CPU的执行资源(先执行调用了join方法的线程)
		try {
			t1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		// 主线程打印10遍名字
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName());
		}
		System.out.println("主线程结束了");
		
	}

}
//  第一个线程循环打印10次名字
class TestRun implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName());
		}
	}
}

// 第二个线程死循环打印名字
class TestARun implements Runnable{

	@Override
	public void run() {
		while (true) {
			System.out.println("测试");
		}
		
	}
	
}

二.同步代码块的弊端与好处

  • 好处:保证了共享数据(代码)安全。
  • 弊端:线程在进入和出去代码块时,增加了拿锁和还锁的操作,比原来更耗费系统资源。
    弊端:不管这个对象被创建出来没有,都会进入同步代码块(即拿锁和还锁的操作)。
    提高效率:(加入判断)如果已经创建出来一次,下次就不让线程再进入同步代码块。

三.接口回调

案例

  • 接口回调(调用接口中方法)
  • 键盘输入1或2
  • 1.红色打印
  • 2.黑色打印
  • 打印内容"接口回调"
    代码:
public class Kll {
	public static void main(String[] args) {
		System.out.println("请输入1或2:");
		Scanner sc = new Scanner(System.in);
		String str = sc.nextLine();
		PrintInterA pA = null;
		if (str.equals("1")) {
			pA = new RedPrint();
		}else {
			pA = new BlackPrint();
		}
		// 调用功能类方法
		Print.printString(pA);
	}

}

// 接口 打印方法
interface PrintInterA {
	public abstract void print(String string);
}
// 功能类 打印字符串的方法
// 方法中使用接口方做参数
class Print {
	public static void printString(PrintInterA pA) {
		// 调用接口中的抽象方法
		// 调用的是实现类传进来的实现类的方法
		// 参数传给了实现类的string
		pA.print("接口回调");
	}
}

// 接口实现类
// 红色打印
class RedPrint implements PrintInterA{

	@Override
	public void print(String string) {
		System.err.println(string);
		
	}	
}
// 黑色打印
class BlackPrint implements PrintInterA{
	
	@Override
	public void print(String string) {
		System.out.println(string);
		
	}	
}

案例需求:

  • 利用接口回调,完成下列操作
  • 主线程处理任务
  • 子线程去读文件,并且将文件(Kll1.java)打印在控制台
    Kll1类代码:
package kll.hd;
public class Kll1 {
	public static void main(String[] args) {
		System.out.println("逻辑任务一");
		System.out.println("逻辑任务二");
		
		//调用功能类中的方法 开子线程读文件
		ReadFile.readFile(new ReadFileInter() {
			
			@Override
			public void readString(String string) {
				// 参数string 就是文件读取的字符串
				// 刷新界面(就是打印一下)
				System.out.println(string);
			}
		});
		
		System.out.println("逻辑任务三");
		System.out.println("逻辑任务四");
	}

}

接口代码:

package kll.hd;
// 读取字符串
public interface ReadFileInter {
	public abstract void readString(String string);

}

功能类

package kll.hd;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

// 功能类,参数时是接口
// 用户看的见的,都在主线程操作
// 用户看不见的,耗时的都要在子线程去操作
public class ReadFile {
	public static void readFile(ReadFileInter inter) {
		// 开个子线程去读文件
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// 读文件 相对路径即可
				File file = new File("src/kll/hd/Demo02.java");
				BufferedReader br = null;
				// 拼接字符串用
				StringBuffer sb = new StringBuffer();
				try {
					FileReader fr = new FileReader(file);
					br = new BufferedReader(fr);
					String string = null;
					while ((string = br.readLine()) != null) {
						// 拼接字符串
						sb.append(string);
						sb.append("\n");
					}
					// 利用方法中传进来的参数
					// 代用接口中的方法
					inter.readString(sb.toString());
				} catch (FileNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}finally {
					if (br != null) {
						try {
							br.close();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
				
			}
		}).start();;
	}

}

四.完整版的懒汉式单例

class Lazy{
	private static Lazy lazy = null;
	private Lazy() {
	}
	public static Lazy getLazy() {
		// 休眠一秒(扩大问题)
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		// 弊端:不管这个对象被创建出来没有,都会进入同步代码块(即拿锁和还锁的操作)
		// 提高效率,如果已经创建出来一次
		// 下次就不让线程再进入同步代码块
		if (lazy == null) {
			synchronized (Lazy.class) {
				if (lazy == null) {
					lazy = new Lazy();
				}
			}
		}
		return lazy;
	}
}

猜你喜欢

转载自blog.csdn.net/KongLingLei_08225/article/details/82792438