两种使用方式:
主要优点:充分利用CPU空闲时间片,用尽可能短的时间完成用户的请求,即使程序的响应速度更快。
表示CPU计算资源在不同线程中切换。有不同的调度方式:
实现多线程有两种方式:
方式一:
public class Thread1Demo {
public static void main(String[] args) {
//2.创建线程对象
MyThread myThread = new MyThread();
//3.启动线程
myThread.start();
//主线程代码
for (int i = 0; i < 30; i++) {
System.out.println("======主线程=======" + i);
}
}
}
//1.编写线程类,继承Thread类
class MyThread extends Thread{
//重写run方法,即线程的任务
@Override
public void run() {
System.out.println("线程任务启动");
for (int i = 0; i < 30; i++) {
System.out.println("分支线程" + i);
}
}
}
注意事项:
方式二:
public class Thread2Demo {
public static void main(String[] args) {
A a = new A();
Thread thread = new Thread(a);
thread.start();
//主线程代码
//Thread thread = Thread.currentThread();
for (int i = 0; i < 30; i++) {
System.out.println("======主线程=======" + i);
}
}
}
class A implements Runnable{
@Override
public void run() {
//System.out.println("线程任务启动");
for (int i = 0; i < 30; i++) {
System.out.println("分支线程" + i);
}
}
}
注:使用接口创建多个线程,方便共享数据,多个线程可以都使用同一个线程任务类启动线程,在后期线程的同步机制中也更为便于锁的实现。
当创建一个线程,只会使用一次后就不会再使用了,可以使用匿名内部类方式实现。
public class Demo3 {
public static void main(String[] args) {
//方式1匿名写法:
new Thread(){
@Override
public void run() {
System.out.println("线程任务A");
}
}.start();
//方式2匿名写法:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程任务B");
}
}).start();
}
}
public Thread()
public Thread(String name)
public Thread(Runnable target);
run()
: 定义线程执行任务isAlive()
: 判断当前线程是否还在活动状态currentThread()
: 返回当前线程对象的引用setName()
:设置线程名称线程优先级最高10,最低1,主线程默认优先级为5,主线程创建的子线程默认优先级也为5.
注: 礼让有可能并没有礼让成功,如果没有其他线程使用CPU资源,原线程还会继续执行。
线程可以分为两类:用户线程和守护线程。用户线程都结束时,守护线程也会随着用户线程的结束而终止。
设置守护线程的方法:
JVM中的垃圾回收器就是一个守护线程,守护的就是程序就是用户线程,程序结束时,守护线程就好自动结束。
线程生命周期的五种状态:新建、就绪、运行、阻塞、死亡
JDK将线程状态分为6种。其中就绪和运行合并为可运行状态,阻塞状态分为了三种:定时等待状态、无限等待状态、等待监视器锁。
使用多个线程模拟多个窗口的卖票过程,保证卖票过程的正常运行。
public class Demo {
public static void main(String[] args) {
new TicketSell("窗口1").start();
new TicketSell("窗口2").start();
new TicketSell("窗口3").start();
}
}
class TicketSell extends Thread{
static int num = 100;
public TicketSell(String name) {
super(name);
}
@Override
public void run() {
//int num = 100;//局部变量
while(num > 0) {
System.out.println(Thread.currentThread().getName()+"卖出了第"+num+"张票");
num -- ;
}
System.out.println("=====买完了=====");
}
}
public class Demo2 {
public static void main(String[] args) {
TicketSell2 sell2 = new TicketSell2();
new Thread(sell2, "窗口1").start();
new Thread(sell2, "窗口2").start();
new Thread(sell2, "窗口3").start();
}
}
class TicketSell2 implements Runnable{
static int num = 100;
@Override
public void run() {
//int num = 100;//局部变量
while(num > 0) {
System.out.println(Thread.currentThread().getName()+"卖出了第"+num+"张票");
num -- ;
}
System.out.println("=====买完了=====");
}
}
运行结果:同样出现线程安全问题,这里就不再贴图了。
出现线程安全问题的前两个条件无法改变,我们就是想让多线程去访问共享数据。故只能对第三个条件进行处理,其中sychronized关键字来将访问共享数据的操作变成原子操作。同步代码块要越小越好,同步代码块越大,效率越低。代码块最小时,应该包含所有操作共享数据的代码,其中本问题中的num
就是共享数据。必须将判断num
大小和对num--
的代码都包含在代码块中才能解决线程安全问题。
public class Demo2 {
public static void main(String[] args) {
TicketSell2 sell2 = new TicketSell2();
new Thread(sell2, "窗口1").start();
new Thread(sell2, "窗口2").start();
new Thread(sell2, "窗口3").start();
}
}
class TicketSell2 implements Runnable{
static int num = 100;
@Override
public void run() {
//int num = 100;//局部变量
while(true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this) {//this就是锁对象
if(num > 0)//共享数据
System.out.println(Thread.currentThread().getName()+"卖出了第"+num--+"张票");//共享数据
}
if(num <= 0) {
break;
}
}
System.out.println("=====买完了=====");
}
}
当线程获取到锁对象才能执行同步代码,在执行完同步代码时会持有锁对象,执行完同步代码后才会释放锁对象。
因此,在使用同步代码块解决线程安全问题时,必须保证锁对象是同一个实例。如果上面代码中的synchronized (this)
更换为new Object()
时就仍然会出现线程安全问题。
把共享数据和操作共享数据的方法封装到一个类中,作为共享资源类,与线程类解耦合。还是以买票问题为例子,将该问题中的买票和操作共享数据分成两个类来处理。
共享资源类:
public class Ticket{
private int num = 100;//共享数据
//操作共享数据的方法
public synchronized void sale(){
if(num > 0)
System.out.println("卖出了第" +(num--)+"张票");
}
//get-set方法
}
线程类:
public class TicketThread extend Thread{
private Ticket ticket;//共享资源
public TicketThread(String name, Ticket ticket){
super(name);
this.ticket = ticket;
}
@Override
public void run(){
while(true){
ticket.sale();
if (ticket.getNum() <= 0){
return ;
}
}
}
}
在实现单例模式时,懒汉式是先判断当前对象实例是否已经创建了,如果为空就创建一个新的对象,否则就new
一个唯一的实例。如果在多线程的情况下, 由于判断是否为空和创建对象实例不是原子操作,在CPU资源切换时有可能导致单例模式失效的问题,也需要用到同步代码块。
public class Singleton {
//私有的静态变量
private static Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
//如果目前对象还未创建,就进入同步代码块,
if(instance == null){//双重校验
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
//否则直接返回实例对象即可
return instance;
}
}
多个线程间的一种协作机制,最经典的就是生产消费者模型,当生产者的商品还没生产出来时,消费者进入等待状态,当商品生产好后再将消费者线程唤醒。
其中等待和唤醒的方法是Object类下面的方法:
wait()
:会释放锁, sleep()
方法不释放锁notify()
: 还有另外一个唤醒所有等待的线程的方法,notifyAll()
这里使用一个厨师和服务员的情景来应用生产者消费者模型。
共享资源类(锁对象):
public class WorkBench {
private int num; //工作台当前餐数
private final int MAX = 10;
private final int MIN = 0;
//放一份快餐到工作台
public synchronized void put(){
while(num >= MAX) {
//等待
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName()+"放了一份快餐到工作台,当前共"+(++num)+"份");
//休息一下
try {
int random = (int)(Math.random() * 1000);
Thread.sleep(random);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.notify();
}
//取快餐
public synchronized void take(){
while(num<=MIN){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName()+"取走了一份快餐,当前共"+(--num)+"份");
//随机休息一下
try {
int random = (int)(Math.random() * 1000);
Thread.sleep(random);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//随机唤醒在此监视器上等待的一个线程
this.notify();
}
}
线程任务类:
厨师类
public class Cooker extends Thread{
private WorkBench workBench;
public Cooker(String name, WorkBench workBench) {
super(name);
this.workBench = workBench;
}
@Override
public void run() {
while (true){
try {
int random = (int)(Math.random() * 1000);
Thread.sleep(random);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
workBench.put();
}
}
}
服务员类
public class Waiter extends Thread{
private WorkBench workBench;
public Waiter(String name, WorkBench workBench) {
super(name);
this.workBench = workBench;
}
@Override
public void run() {
while (true){
try {
int random = (int)(Math.random() * 1500);
Thread.sleep(random);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
workBench.take();
}
}
}
测试类:
public class Demo {
public static void main(String[] args) {
WorkBench wb = new WorkBench();
new Cooker("大厨",wb).start();
new Waiter("翠花",wb).start();
}
}
这个版本的代码如果修改为多个生产者多个消费者模型,可以将notify方法改为notifyAll,当服务员取走快餐时,唤醒所有厨师类进行工作。