javaSE基础学习笔记 day12 线程创建的其他方法 线程安全 线程通信

javaSE基础学习笔记 day12

  • 解决线程安全的方式
  • 线程通信
  • JDK5.0新增的线程创建方式

解决线程安全的方式

  1. 同步代码块
    同步监测器:俗称“锁”,任何一个类的对象都可以充当锁。
    要求:多个线程必须共用一把锁,才能达到同步的效果。
synchronized(同步监测器){
     
	//需要被同步的代码(操作共享数据的代码)
}
  1. 同步方法
    如果操作的共享数据的代码刚好在完整的一个方法中,则可以将这个方法声明为 synchronized 的,表面该方法为同步方法。
    注意:
    非静态同步方法的同步监测器默认为 this
    静态方法的同步监测器默认为当前类本身。

  2. 使用 Lock 锁 – JDK5.0新增
    ①实例化 RenntranLock 的对象。
    ②调用 lock() 方法,作为同步代码的开头。
    ③调用 unlock() 方法,作为同步代码的结尾。

  3. synchronized 与 Lock 的区别
    synchronized 在执行完相应的同步代码区后,自动释放同步监测器,而 Lock 需要手动启动同步,手动结束同步。

  4. 懒汉式的线程安全版本(高效率)

class Bank{
     
	private static Bank instance = null;
	//私有化构造器
	private Bank(){
     }
	public static Bank getInstance(){
     
		if(instance == null){
     
			synchronized(Bank.class){
     
				if(instance == null)
					instance = new Bank();
			}
		}
		return instance;
	}
}

线程通信

  1. wait():一旦执行此方法,当前进程进入阻塞态,并释放同步监测器
  2. notify():一旦执行此方法,就会唤醒一个被 wait() 的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程。
  3. notifyAll():一旦执行此方法,就会唤醒所有被 wait() 的线程。
    注意:
    ①这三个方法只能在同步代码块和同步方法中调用。

    ②这三个方法的调用者必须是当前同步代码块和同步方法的同步监测器,否则会报异常。

sleep() 与 wait() 的异同:
相同点:调用后都会使线程进入阻塞态。
不同点:
①二者的声明位置不同:Thread 中声明 sleep() ,Object 中声明 wait() 。
②调用的场景不同:sleep() 可以在任意位置调用,wait() 只能在同步代码块和同步方法中调用。
③若二者都在同步代码块和同步方法中被调用,wait() 会释放同步检测器。

JDK5.0新增的线程创建方式

  1. 实现 Callable 接口
    ①创建 Callable 的实现类。
    ②实现 call() 方法,将此线程想要完成的操作声明在 call() 方法中。
    ③创建 Callable 实现类的对象。
    ④将此实现类的对象作为参数传递到 FutureTask 的构造函数中,创建对象。
    ⑤将 FutureTask 的对象作为参数传递到 Thread 的构造函数中,创建对象,并 start() 。
    ⑥如需要 call() 方法的返回值,可调用 FutureTask 的对象的 get() 方法。
public class CallableTest {
     
    public static void main(String[] args) {
     
        Callable c1 = new Hello();
        FutureTask f1 = new FutureTask(c1);
        Thread t1 = new Thread(f1);
        t1.start();
    }
}

class Hello implements Callable{
     
    @Override
    public Object call() throws Exception {
     
        //执行的逻辑
        return null;
    }
}

为什么说 Callable 比 Runnable 强大?
① call() 可以有返回值。
② call() 可以抛出异常。
③ call() 是支持泛型的。

  1. 使用线程池
    经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
    思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
class Hello1 implements Runnable{
     
    @Override
    public void run() {
     
        //执行的逻辑
    }
}
public class ThreadPool {
     
    public static void main(String[] args) {
     
        ExecutorService e1 = Executors.newFixedThreadPool(10);
        Hello1 hello = new Hello1();
        e1.execute(hello);//适合Runnable
//        e1.submit(); 适合Callable
		//最后不要忘记关闭连接池
        e1.shutdown();
    }
}

你可能感兴趣的:(java,java,多线程,编程语言)