多线程
多线程的两个概念
并发:在同一个cpu内多个线程同时发生
并行:在多个cpu内多个线程同时进行
多线程的三种实现方式
1.继承Thread
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName()+":"+i);
}
}
}
测试代码
@Test
public void test01(){
MyThread myThread1 = new MyThread();
myThread1.setName("线程1");
MyThread myThread2 = new MyThread();
myThread2.setName("线程2");
myThread1.start();
myThread2.start();
}
2.实现Runnable接口
public class MyRun implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
测试代码
@Test
public void test02(){
MyRun myRun = new MyRun();
Thread th1 = new Thread(myRun);
Thread th2 = new Thread(myRun);
th1.setName("线程1");
th2.setName("线程2");
th1.start();
th2.start();
}
3.利用Callable接口和Future接口
//Callable中的泛型指定线程要返回的结果
public class MyCall implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Integer sum=0;
for (int i = 1; i <= 100; i++) {
sum+=i;
}
return sum;
}
}
测试代码
@Test
public void test03() throws ExecutionException, InterruptedException {
//创建MyCallable对象
MyCall myCall1 = new MyCall();
//创建FutureTask(Future接口的实现类)对象,用于接收线程执行完成后返回的结果
FutureTask<Integer> futureTask1 = new FutureTask<Integer>(myCall1);
//创建线程,开启线程
Thread th1 = new Thread(futureTask1);
th1.start();
//获取返回值
System.out.println("result:"+futureTask1.get());
}
总结
实现Callable接口可以返回线程执行的结果
多线程中常用的成员方法
线程的优先级
线程的优先级默认是5,最大是10,最小是1。(10表示优先级最高)
@Test
public void test04(){
MyThread myThread1 = new MyThread("线程1");
MyThread myThread2 = new MyThread("线程2");
System.out.println(myThread1.getPriority()+"-----"+myThread2.getPriority());//默认优先级
//设置优先级
myThread1.setPriority(1);
myThread2.setPriority(10);
myThread1.start();
myThread2.start();
}
守护线程
测试代码及结果
应用场景
礼让线程
线程1和线程2每进入一次循环打印输出都都进行礼让,使得打印结果相较之前更加均匀了
插队线程
结果为线程1执行完毕后main线程才能执行并打印输出
线程的生命周期
同步代码块
引用同步代码块之前
public class Ticket extends Thread{
private static Integer num=0;//所有类创建的对象都共享100张票
public Ticket() {
}
public Ticket(String name) {
super(name);
}
@Override
public void run() {
while (num<100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
}
}
public static void main(String[] args) {
Ticket t1 = new Ticket("窗口1");
Ticket t2 = new Ticket("窗口2");
Ticket t3 = new Ticket("窗口3");
t1.start();
t2.start();
t3.start();
}
}
测试结果
出现重复票,票数超过100张,出票顺序错误的情况,这些都是由于线程抢占CPU执行权导致出票流程没有一气呵成。
引入同步代码块之后
首先(错误示范)
@Override
public void run() {
synchronized (lock) //把需要一气呵成的代码包裹起来
{
while (num<100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
}
}
}
注意,
synchronized同步代码块不能写在while循环的外面,否则第一个抢占到CPU的线程将会把while循环执行完,会出现如下结果(一个窗口自己把票卖完了),运行结果如下
修改后
@Override
public void run() {
while (num<100){
synchronized (lock) //把需要一气呵成的代码包裹起来
{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
}
}
}
此外,在循环内部需要重新检查票数是否超过,否则在num=99时,三个线程都同时进入while循环,他们最终都会执行一次同步代码块,会出现如下结果
最终代码及测试结果
package com.example.testThread;
public class Ticket extends Thread{
private static Integer num=0;//所有类创建的对象都共享100张票
private static Object lock=new Object();//这个lock必须是唯一的
public Ticket() {
}
public Ticket(String name) {
super(name);
}
@Override
public void run() {
while (num<100){
//把需要一气呵成的代码包裹起来
//此处也可以用 Ticket.class 因为这个类的字节码被加载到内存中也是唯一的
synchronized (Ticket.class)
{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num<100) //重新判断一次
{
System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
}
else {
break;
}
}
}
}
public static void main(String[] args) {
Ticket t1 = new Ticket("窗口1");
Ticket t2 = new Ticket("窗口2");
Ticket t3 = new Ticket("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法
案例(用同步方法实现上述买票案例)
只需要将用synchronized包裹住的代码块封装成一个方法.
先用synchronized确认要包裹的共享代码块。
package com.example.testThread;
public class MyTicketRunnable implements Runnable{
//因为继承了Runnable接口,多个线程只需要调用同一个MyTicketRunnable即可完成买票流程,这里的num也不需要用static修饰了
int num=0;
@Override
public void run() {
while (num<100){
synchronized (MyTicketRunnable.class){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num<100){
System.out.println(Thread.currentThread().getName()+"正在卖第"+(++num)+"张票");
}
else{
break;
}
}
}
}
public static void main(String[] args) {
MyTicketRunnable mtr = new MyTicketRunnable();
Thread t1 = new Thread(mtr,"线程1");
Thread t2 = new Thread(mtr,"线程2");
Thread t3 = new Thread(mtr,"线程3");
t1.start();
t2.start();
t3.start();
}
}
封装成一个synchronized方法
(1.选中,ctrl+alt+m 生成方法,手动添加synchronized
2.再删掉原来包裹它的synchronized)
package com.example.testThread;
public class MyTicketRunnable implements Runnable{
//因为继承了Runnable接口,多个线程只需要调用同一个MyTicketRunnable即可完成买票流程,这里的num也不需要用static修饰了
int num=0;
@Override
public void run() {
while (num<100){
if (synMethod()) break;
}
}
//这里创建的是MyTicketRunnable的实例化对象mtr(动态方法),因此这里的锁是mtr(this)
private synchronized boolean synMethod() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num<100){
System.out.println(Thread.currentThread().getName()+"正在卖第"+(++num)+"张票");
}
else{
return true;
}
return false;
}
public static void main(String[] args) {
MyTicketRunnable mtr = new MyTicketRunnable();
Thread t1 = new Thread(mtr,"线程1");
Thread t2 = new Thread(mtr,"线程2");
Thread t3 = new Thread(mtr,"线程3");
t1.start();
t2.start();
t3.start();
}
}
lock锁
案例
将同步代码块的案例(Ticket)中的run方法中的共享代码块用锁包裹
static Lock lock=new ReentrantLock();//创建静态锁对象
@Override
public void run() {
//线程1卖完第100张票后回到这里,判断num=100直接结束了
while (num<100){
//线程3会卡死在这里
lock.lock();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num<100) //重新判断一次
{
System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
}
else {
break;//num=100时,线程2直接跳出去了,没有unlock
}
lock.unlock();
}
}
执行结果
能够正常售票,但是程序没有停止。
这是因为执行到num=100时(第100张票已经被线程1卖出),假设是线程2抢到了cpu的执行权,它进行判断num=100直接break,没有unlock,这时候线程3就被卡住了,该程序永远不会结束。
正确的做法
@Override
public void run() {
while (num<100){
lock.lock();
try {
Thread.sleep(10);
if(num<100) //重新判断一次
{
System.out.println(Thread.currentThread().getName()+"售出第"+(++num)+"张票");
}
else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();//无论如何都要释放锁
}
}
}
无论如何都要释放锁
死锁
死锁的定义
-
死锁是指在执行过程中,两个或两个以上的进程(或线程)由于竞争资源或彼此通信而阻塞,导致无法继续执行的情况。
-
如果没有外部干预,这些进程将无法向前推进。
-
这种状态被称为系统死锁或死锁产生。
-
这些相互等待的进程被称为死锁进程。
产生死锁的4个条件
1、互斥条件:一个共享资源同一时刻只允许有一个线程使用,如果其他线程要使用,要等它使用完成后释放。
2、请求并持有条件:一个线程已经持有了至少一个资源,又请求了新的资源,而新的资源已经被其他线程占用。
3、不可剥夺条件:线程获取到的资源在使用完之前不会被其他线程所抢占,只能由自己释放。
4、环路等待:必然存在一个环形链,T0等T1,T1等T0
*******************************************************************
案例
可能发生死锁