线程的同步
方式一.同步代码块
synchronized(同步监视器){
、、、需要同步的代码,即操作共享数据的代码
}
同步监视器:俗称:锁。任何一个类的对象都可以充当锁
要求多个线程必须共用同一把锁
补充:在实现Runnable接口创建多线程方式中,可以考虑使用this作为监视器
但是在继承Thread类创建多线程的方式中,慎用this充当监视器,考虑使用当前类作为监视器
方式二.同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步方法
用synchronized修饰方法
①同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
②非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
懒汉式的同步问题
public class SingletonTest2 {
public static void main(String[] args) {
Order b1 = Order.getInstance();
Order b2 = Order.getInstance();
System.out.println(b1 == b2);
}
}
class Order{
//1.私有化类的构造器
private Order() {}
//2.声明当前类的对象,也是必须为static,没有初始化
private static Order instance = null;
//3.声明public、static的返回当前类对象的方法
public static Order getInstance() {
//方法1,效率稍差
synchronized(Bank.class){
if(instance == null){
instance = new Order();
}
return instance;
}
//方法2
if(instance == null){
synchronized(Bank.class){
instance = new Order();
}
}
return instance;
}
LOCK(锁)
1.实例化ReentrantLock
2.上锁,调用lock();
3.解锁,调用unlock();
面试题:synchronized和ReentrantLock的异同
synchronized机制在执行完相应的同步代码后,自动的释放同步监视器
Lock需要手动启动同步和结束同步
线程通信
1.wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
2.notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个
3.notifyAll():一旦执行此方法,就会执行所有被阻塞的线程
说明:
1.上述三个方法必须使用在同步代码块中
2.上述三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则出现异常
3.上述三个方法是定义在object 类中,而不是Thread类
面试题
sleep()和wait()的异同
1.相同点:一旦执行方法,都可以使当前的进程进入阻塞状态
2.不同点:
①两个方法声明的位置不同:Thread中声明sleep(),Object中声明wait()
②调用的要求不同,sleep()可以在任何需要的场景下调用,wait()必须在同步代码块和同步方法中调用
3.关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
生产者消费者问题
package javaSenior;
import com.oracle.jrockit.jfr.Producer;
/**
* 生产者消费者问题
* @return
* @author zhangyq
* @date 2021/2/23 0023 18:26
*/
public class product {
public static void main(String[] args) {
Clerk clerk = new Clerk();
producter p1 = new producter(clerk);
customer c1 = new customer(clerk);
Thread t2 = new Thread(c1);
Thread t1 = new Thread(p1);
Thread t3 = new Thread(c1);
t1.setName("生产者1号");
t2.setName("消费者1号");
t3.setName("消费者2号");
t1.start();
t2.start();
t3.start();
}
}
class Clerk{
private int num = 0;
public synchronized void produceProduct(){
if(num<20) {
num++;
System.out.println(Thread.currentThread().getName()+"开始生产第:"+num+"个产品");
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumeProduct() {
if(num>0){
System.out.println(Thread.currentThread().getName()+"开始消费第:"+num+"个产品");
num--;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class customer implements Runnable{
private Clerk clerk;
public customer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
while(true) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
class producter implements Runnable{
private Clerk clerk;//Clerk clerk = new Clerk();与Clerk clerk;的区别,此处只是定义了一个Clerk类型的引用
//Clerk clerk = new Clerk();首先定义了一个Clerk类型的引用clerk,并且使用Clerk的构造方法构造了一个
//Clerk类型的对象,然后令clerk指向此对象
public producter(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
注意:Clerk clerk = new Clerk();与Clerk clerk;的区别,此处只是定义了一个Clerk类型的引用Clerk clerk = new Clerk();首先定义了一个Clerk类型的引用clerk,并且使用Clerk的构造方法构造了一个Clerk类型的对象,然后令clerk指向此对象
JDK5.0新增的线程创建方式
加上新增的两种,一共有四种创建线程方式
1.继承Thread类
2.实现Runnable接口
3.实现callable接口
4.线程池
callable与Runnable相比
① 相比run(),可以有返回值
②方法可以抛出异常
③支持泛型的返回值
④需要借助FutureTask类,比如获取返回结果
Future接口
可以对具体Runnable、callable任务的执行结果进行取消、查询是否完成、获取结果等
FurureTask是Future接口的唯一的实现类
FutureTask同时实现了Runnable、Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
get()返回值即为FutureTask构造器参数callable实现类重写的call()的返回值
Callable接口创建进程步骤
1.创建一个实现Callable的实现类
2.实现call方法,将此线程需要执行的操作声明在call()中
3.创建callable接口实现类的对象
4.将此callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
6.获取callable中的call方法的返回值
package javaSenior;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程的方式三:实现callable接口
* @return
* @author zhangyq
* @date 2021/2/23 0023 19:42
*/
public class ThreadTest4 {
public static void main(String[] args) {
test4 t1 = new test4();
FutureTask f1 = new FutureTask(t1);
new Thread(f1).start();
try {
Object sum = f1.get();//get()返回值即为FutureTask构造器参数callable实现类重写的call()的返回值
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
;
}
}
class test4 implements Callable{
@Override
public Object call() throws Exception {
int sum = 0;
for(int i = 0;i<100;i++){
if(i % 2 == 0) {
System.out.println(i);
sum = sum + i;
}
}
return sum;
}
}
如何理解实现callable接口的方式创建多线程比实现runnable接口创建线程方式更强大
1.call()可以有返回值
2.call()可以抛出异常,被外面的操作捕获
3.callable支持泛型
线程池方式
1.corePoolSize:核心池的大小
2.maximumPoolSize:最大线程数
3.keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池实现步骤
1.提供指定线程数量的线程池
2.执行指定的线程的操作,需要提供实现runnable接口或callable接口实现类的对象
3.关闭连接池
package javaSenior;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 使用线程池
* @return
* @author zhangyq
* @date 2021/2/24 0024 21:12
*/
class numberTest implements Runnable {
@Override
public void run() {
int sum = 0;
for(int i = 0;i<100;i++){
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName());
sum = sum + i;
}
}
}
}
public class ThreadPoolTes {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//2.执行指定的线程的操作,需要提供实现runnable接口或callable接口实现类的对象
executorService.execute(new numberTest());//适合用于runnable
//executorService.submit();//适合用于callable
//3.关闭连接池
executorService.shutdown();
}
}