线程
多线程的好处:多个线程互不影响(因为在不同的栈空间)
Thread类
java.lang.Thread
类
构造方法:
-
public Thread()
:分配一个新的线程对象。 -
public Thread(String name)
:分配一个指定名字的新的线程对象。 -
public Thread(Runnable target)
:分配一个带有指定目标新的线程对象。 -
public Thread(Runnable target,String name)
:分配一个带有指定目标新的线程对象并指定名字。
常用方法:
-
public String getName()
:获取当前线程名称。 -
public void start()
:导致此线程开始执行; Java虚拟机调用此线程的run方法。 -
public void run()
:此线程要执行的任务在此处定义代码。 -
public static void sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。 -
public static Thread currentThread()
:返回对当前正在执行的线程对象的引用。直接打印[Thread-0,5,main]
分别代表:线程名,优先级,在哪个方法
创建多线程的第二种方式:
1.创建一个实现类来实现Runnable接口并重写run方法
package com.qin.study;
/**
* Created by SunYuqin in 2018/8/8
* Description:
**/
public class RunnableImpl implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
2.通过Thread中的构造创建线程
public class RunnableImplTest {
public static void main(String[] args) {
Runnable runnable = new RunnableImpl();
Thread td = new Thread(runnable);
td.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
采用java.lang.Runnable
,只需要重写run方法
定义Runnable的实现类或者是匿名内部类,重写run方法,使用Thread的构造方法实例作为Thread的target对象来创建线程对象.
实现Runnable借口创建多线程程序的好处:
-
避免了单继承的局限性:
一个类只能继承一个类,类继承了Thread就不能继承其他类,而实现了Runnable借口还可以继承其他类
-
增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
-
适合多个相同的程序代码的线程去共享同一个资源
-
线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
匿名内部类实现线程的创建
package com.qin.study;
/**
* Created by SunYuqin in 2018/8/8
* Description:
**/
public class RunnableNoName {
public static void main(String[] args) {
//Thread类的匿名创建
new Thread(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}.start();
//Runnable接口的形式匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}).start();
}
}
后来可以使用lamada表达式简化
线程安全
线程安全
多线程访问了共享的数据会产生安全问题
问题:电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。
在不考虑安全问题的代码实现
//Runnable的实现类
public class Ticket implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖"+ticket--);
}
}
}
}
//Test类
public class TicketTest {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread thread1 = new Thread(ticket,"窗口1");
Thread thread2 = new Thread(ticket,"窗口2");
Thread thread3 = new Thread(ticket,"窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
会出现两个问题:
-
相同的票数,比如5这张票被卖了两回。
-
不存在的票,比如0票与-1票,是不存在的。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程同步
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。
同步技术的原理
使用一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视锁
3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法进行卖票
-
t0抢到了cpu的执行权,执行run方法,遇到synchronized代码块
这时t0会检查synchronzed代码块是否有锁对象,发现有,就会获取到锁对象,进入到同步中执行,出了同步会把锁对象归还.
-
t1到了cpu的执行权,执行run方法,遇到synchronized代码块
这时t0会检查synchronzed代码块是否有锁对象,发现没有,就会进入到阻塞状态,会一直等待t0线程归还锁对象...
同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁,就进不去同步
同步保证了只能有一个线程在同步中处理共享数据.保证了安全性
但是在频繁的判断锁,获取锁,释放锁,程序的效率会降低
有三种操作:
-
同步代码块
-
同步方法
-
锁机制
-
1.同步代码块:
格式
synchronized(锁对象){
可能会出现安全问题的代码(访问了)
}
-
通过代码块中的锁对象,可以使用任意的对象(引用类型),也可以使用"abc".因为是静态的,能保证同一个
-
但是必须保证多个线程使用的锁对象是同一个
-
在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
-
锁对象的作用:
把同步代码块锁住,只让一个线程在同步代码块中运行
修改同步部分的代码使用同步代码块的方法:
public class Ticket implements Runnable {
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj){
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖"+ticket--);
}
}
}
}
}
-
2.同步方法
同步方法也会把方法内部的代码锁住,只让一个线程执行,同步方法的锁对象是谁?
-->就是实现对象new RunnableImpl(),也就是this
-
把访问了共享数据的代码抽取出来,放在一个方法中
-
在方法上添加synchronzed修饰符
修改同步部分的代码使用同步方法的方法:
public class Ticket implements Runnable {
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
ticketSell();
}
}
public void ticketSell() {
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖"+ticket--);
}
}
}
静态方法的话,要注意加上static
静态同步方法:
-
锁对象是谁?
不能是this,this是创建对象之后产生的,而静态方案优先于对象,所以静态方法的锁对象是本类的class属性-->class文件对象(反射)
-
就是:Runnable.class
-
3.lock锁机制
java.util.concurrent.locks.Lock
接口机制提供了比synchronized代码块synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:public void lock()
:加同步锁。public void unlock()
:释放同步锁。
使用步骤:
-
在成员位置创建一个ReentrantLock对象
-
在可能会出现安全问题的代码前调用Lock接口的方法lock获取锁
-
在可能会出现安全问题的代码后调用ock接口的方法unlock释放锁
有一个写法,最好配合使用finally代码块:无论什么情况,最后都释放锁.
public class Ticket implements Runnable {
private int ticket = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖" + ticket--);
}
lock.unlock();
}
}
}
线程状态
线程状态概述:
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TimedWaiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
几个方法的使用:
wait的作用:1.线程等待 2.释放锁
而sleep是没有释放锁的功能的
wait与notify只能是锁对象才可以调用
wait(long time)就相当于sleep(long time)
notify():如果有多个等待线程,随机唤醒一个
notifyAll():同时唤醒所有等待的线程
生产者,消费者使用wait和notify来观察线程的状态
代码实现
package com.qin.study;
/**
* Created by SunYuqin in 2018/8/8
* Description:
* 消费者:买饭,wait,吃
* 生产者:卖饭,notify,做饭
**/
public class MealDemo {
public static void main(String[] args) {
Object obj = new Object();
//消费者线程
new Thread() {
@Override
public void run() {
while (true) {
synchronized (obj) {
System.out.println("老板,来份饭 !!!");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始吃饭,吃完了结账.");
}
}
}
}.start();
//生产者线程
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("好嘞,开始做饭");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("饭做好了,您请用!");
obj.notify();
}
}
}
}.start();
}
}
注:两个线程是随机运行的.也可能会一直跑一个线程.