继承Thread类
继承Thread类创建并启动多线程的步骤:
①.定义一个类,继承自Thread类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此,run方法的方法体被称为线程执行体
②.创建Thread子类的对象,即创建了子线程
③.用线程对象的start方法来启动该线程
Demo先创建一个售票线程
public class SellTickets extends Thread {
//共享数据
static int count = 100;
@Override
public void run() { //执行的内容
//循环售票
while(count > 0) {
count--;
System.out.println(Thread.currentThread().getName() + "售出了一张票,剩余" + count);
}
}
}
测试类
public class TheadDemo {
public static void main(String[] args) {
//模拟四个售票员售票 开启四个线程 3个是thread 一个是main
SellTickets s1 = new SellTickets();
SellTickets s2 = new SellTickets();
SellTickets s3 = new SellTickets();
// System.out.println(s1.currentThread().getName()); //这个线程的名称是main
s1.start();
s2.start();
s3.start();
}
}
实现Runnable接口
实现Runnable接口创建并启动多线程的步骤:
a.定义一个Runnable接口的实现类,并重写该接口中的run方法,该run方法的方法体同样是该线程的线程执行 体
b.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
c.调用线程对象的start方法来启动该线程
public class fatherDemo implements Runnable{
static int count = 100;
@Override
public void run() {
while(count > 0) {
count--;
System.out.println(Thread.currentThread().getName() + "售出了 一张票,剩余" + count);
}
}
public static void main(String[] args) {
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.start();
t2.start();
t3.start();
}
}
两种实现方式的比较
①实现Runnable接口的方式
a.线程类只是实现了Runnable接口,还可以继承其他类【一个类在实现接口的同时还可以继承另外一个类】
b.可以多个线程共享同一个target对象,所以非常适合多个线程来处理同一份资源的情况
c.弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()
② 继承Thread类的方式
a.编写简单,如果要访问当前线程,除了可以通过Thread.currentThread()方式之外,还可以使用 super关键字
b.弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】 实际上大多数的多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】java类是单知继承的
调用start()与run()方法的区别
Thread thread = new Thread();
thread.start();
① - start()方法会新建一个线程,并且让这个线程执行run()方法。
Thread thread = new Thread();
thread.run();
② - 调用run()也能正常执行。但是,却不能新建一个线程,而是在当前线程中调用run()方法,只是作为一个普通的方法调用
synchronized修饰符
public class MyThread extends Thread {
private int count = 5;
@Override
public synchronized void run() {
count--;
System.out.println(this.currentThread().getName() + " count:" + count);
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread, "thread1");
Thread thread2 = new Thread(myThread, "thread2");
Thread thread3 = new Thread(myThread, "thread3");
Thread thread4 = new Thread(myThread, "thread4");
Thread thread5 = new Thread(myThread, "thread5");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
当多个线程访问MyThread 的run方法的时候,如果使用了synchronized修饰,那个多线程就会以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的),一个线程想要执行synchronized修饰的方法里的代码,首先是尝试获得锁,如果拿到锁,执行synchronized代码体的内容,如果拿不到锁的话,这个线程就会不断的尝试获得这把锁,直到拿到为止,而且多个线程同时去竞争这把锁,也就是会出现锁竞争的问题。
产生原因:
有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了
解决方案:
一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源, 就得在“锁”外面等待
锁
对象锁:任意的对象都可以被当做锁来使用
类锁:把一个类当做锁,语法为:类名.class
同步代码块
synchronized(锁) {
//需要访问临界资源的代码段
}
说明:
- a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待
- b.执行完代码段中的这段代码,会自动解锁。然后剩下的其他线程开始争抢cpu时间片
- c.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义.
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。
public class SellTickets {
static int count = 10;
//任何对象都可以充当一个对象锁
static Object obj = new Object();
static Runnable r = new Runnable() {
@Override
public void run() {
while(count > 0) {
System.out.println(Thread.currentThread().getName()+"--");
synchronized(obj){
count--;
if(count <= 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "售出了 一张票,剩余" + count);
}
}
}
};
public static void main(String[] args) {
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
// t1.setPriority(8);
t1.start();
t2.start();
t3.start();
}
}
同步代码块和类锁的使用
使用类锁的案例如下,参考格式 即 类.class
同步方法
没有加锁的方法
加锁的方法
参考代码如下,大家自行运行实践(ps:可以去除 synchronized关键字)
public class SellTickets {
static int count = 100;
//任何对象都可以充当一个对象锁
//static Object obj = new Object();
static Runnable r = new Runnable() {
@Override
public void run() {
while(count > 0) {
sellTickets();
}
}
//同步方法,作用和同步代码块一样
public synchronized void sellTickets() {
if (count <= 0) {
return;
}
count--;
System.out.println("售票员" + Thread.currentThread().getName() + "售出一张票,余额 为" + count);
}
};
public static void main(String[] args) {
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
// t1.setPriority(8);
t1.start();
t2.start();
t3.start();
}
}
等待唤醒机制(消费者与生产者)
一、生产者消费者问题
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。
该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。
生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
生产消费者模型
生产者消费者模型具体来讲,就是在一个系统中,存在生产者和消费者两种角色,他们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者把资料做成产品。生产消费者模式如下图。
代码实现
案例如下
首先定义一个类 ,这个类创建一个长度为5的数组,然后用synchronized同步锁实现生产数据和消费数据两个方法,两个方法中分别有 wait()和notify();
public class Resources {
private int[] arr = new int[5];
private int count = 0;
/**
* 生产一个数据
*/
synchronized public void product() throws Exception {
if(count == arr.length) {
wait();
}else {
int m = (int)Math.floor(Math.random()*10+1) ;
arr[count] = m;
count ++ ;
System.out.println("生产:"+m);
//唤醒消费者
notify();
}
}
/**
* 消费一个数据
*/
synchronized public void consume() throws Exception {
if(count == 0) {
wait();
}else {
int n = arr[count-1];
arr[count-1] = 0;
count--;
System.out.println("消费:"+n);
//唤醒生产者
notify();
}
}
}
创建生产者线程,引入刚刚的对象,用刚刚的对象调用生产的方法
public class ProduceThread extends Thread {
private Resources r;
public ProduceThread(Resources r) {
this.r = r;
}
public void run() {
try {
while(true) {
r.product();
Thread.sleep(1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建消费者线程,同样引入Resources对象,然后再用这个对象调用消费数据的方法
public class ConsumerThread extends Thread {
private Resources r;
public ConsumerThread(Resources r) {
this.r = r;
}
public void run() {
try {
while(true) {
r.consume();
Thread.sleep(1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试类
public class Test {
public static void main(String[] args) {
Resources r = new Resources();
ProduceThread t1 = new ProduceThread(r);
ConsumerThread t2 = new ConsumerThread(r);
t1.start();
t2.start();
}
}
java中notify 和 notifyAll区别
notify()notifyAll()的共同点:
均能唤醒正在等待的线程,并且均是最后只有一个线程获取资源对象的锁。
notify()notifyAll()的不同点:
notify() 只能唤醒一个线程,而notifyall()能够唤醒所有的线程,当线程被唤醒以后所有被唤醒的线程竞争获取资源对象的锁,其中只有一个能够得到对象锁,执行代码。注意:wait()方法并不是在等待资源的锁,而是在等待被唤醒。
当您在对象上调用 notify 方法时,它会唤醒等待该对象的线程之一。因此,如果多个线程正在等待一个对象,它将唤醒其中一个。现在你一定想知道它会唤醒哪一个。它实际上取决于操作系统的实现。
notifyAll 将唤醒等待该对象的所有线程,不像 notify 只唤醒其中一个。哪个将首先唤醒取决于线程优先级和操作系统实现。
sleep方法和wait方法区别
这个问题常问,sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器
监视器是由Per Brich Hansen和Tony Hoare提出的概念,Java以不精确的方式采用了它,也就是Java中的每个对象有一个内部的锁和内部条件。如果一个方法用synchronized关键字声明,那么,它表现的就像一个监视器方法。通过wait/notifyAll/nofify来访问条件变量
当您在对象上调用 wait 方法时,它会告诉线程放弃锁定并进入睡眠状态,除非并且直到某个其他线程进入同一监视器并在其上调用 notify 或 notifyAll 方法。
演示
package org.arpit.java2blog.thread;
public class Book {
String title;
boolean isCompleted;
public Book(String title) {
super();
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isCompleted() {
return isCompleted;
}
public void setCompleted(boolean isCompleted) {
this.isCompleted = isCompleted;
}
}
线程池
通过Executors类提供的方法。
1、newCachedThreadPool
创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
private static void createCachedThreadPool() {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(() -> {
// 获取线程名称,默认格式:pool-1-thread-1
System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
// 等待2秒
sleep(2000);
});
}
}
newFixedThreadPool
创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
private static void createFixedThreadPool() {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(() -> {
// 获取线程名称,默认格式:pool-1-thread-1
System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
// 等待2秒
sleep(2000);
});
}
}
newScheduledThreadPool
创建一个周期性的线程池,支持定时及周期性执行任务。
private static void createScheduledThreadPool() {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
System.out.println(DateUtil.now() + " 提交任务");
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.schedule(() -> {
// 获取线程名称,默认格式:pool-1-thread-1
System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
// 等待2秒
sleep(2000);
}, 3, TimeUnit.SECONDS);
}
}
newSingleThreadExecutor
创建一个单线程的线程池,可保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
private static void createSingleThreadPool() {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(() -> {
// 获取线程名称,默认格式:pool-1-thread-1
System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
// 等待2秒
sleep(2000);
});
}
}