进程:即正在执行的应用程序
线程:程序功能执行的最小单位(代码运行的通道)
二者关系:一个进程中包含了多个线程(至少一个)
多线程并发: 一个时间段之内,有多个线程执行。
多线程并行:(多cpu情况下) 同一时刻有多个线程同时执行。
Thread的常用方法:
-
sleep(); 里面放入long值,使线程休眠指定毫秒数,使线程堵塞
-
join(); 等待该线程终止 使指定线程暂时处于独占作用,直到执行完毕,使线程死亡,使其他线程堵塞
-
yield(); 线程礼让方法,使线程大概率平均调用 不是绝对的,因为cpu是随机调用线程
-
setPriority(); 更改线程优先级方法 (1~10)数字越大优先级越大,但是设置优先级只是将线程首先被调用的几率无限制的增大,并不是绝对优先100%,即使是优先级为1的线程也有可能首先被调用
-
getPriority(); 查看线程优先级
-
interrupt(); 线程中断方法,但是使用此方法不是像想像中的直接停止线程,而是相当于给这儿线程做了个标记,提示可以停止这个线程,后续使用别的方法进行线程的停止
-
isInterrupted(); 使用此方法可以测试这个线程是否被中断
写在重写的run方法里 其他的写在Test类中
通过继承Thread类创建多线程:
-
创建类,在类中继承Thread类
-
在类中重写run方法
-
创建线程类对象
-
使用Strat()方法开启线程
创建线程类,在类中重写run方法
public class Tread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
}
class Thread2 extends Thread{
@Override
public void run() {
for (int i = 100; i > 0; i--) {
System.out.println(getName()+";"+i);
}
}
}
创建线程对象,开启线程:
public static void main(String[] args) {
//创建线程对象
Tread1 td1 = new Tread1();
td1.setName("海绵宝宝");
Tread1 td2 = new Tread1();
td2.setName("派大星");
Thread2 td3 = new Thread2();
td3.setName("章鱼哥");
//开启多线程
td1.start();
td2.start();
td3.start();
}
线程的执行过程:
线程的分类:
-
主线程:main执行时,会主动开启一条主线程通道
-
用户线程:其他手动创建的线程,如果未设置守护状态,都为用户线程
-
守护线程:如果线程创建后,设置为守护线程,则该线程会在所有用户线程结束后,自动结束
手动创建的线程都为用户线程
守护线程的创建:
使用setDaemon(); 方法创建守护线程,里面填入boolean类型的参数,填入true即为定义该线程为守护线程
-
创建守护线程时,先在重写的run方法中创建一个死循环
-
再使用setDaemon(); 把死循环所在的类的对象设置为守护线程
守护线程多用于:后台程序的运行,因为只要用户线程有没结束的,守护线程就会一直执行下去
代码示例:
public class ThreadDemo extends Thread{
@Override
public void run() {
while (true){
System.out.println(getName()+":正在执行:");
}
}
}
ThreadDemo td = new ThreadDemo();
td.setName("孙悟空");
//设置守护线程
td.setDaemon(true);
此时孙悟空就被设置成了守护线程,只有程序中的用户线程全部结束后,该守护线程猜自动结束
使用Runnable接口创建线程
-
创建类实现Runnable接口
-
重写run方法
-
创建该类的对象
-
创建Thread对象
-
开启线程
使用 Runnable 接口创建线程的好处: 方便进行多个线程间的数据共享
实现Runnable接口的类,无法直接开启线程,需要转为Thread对象,进行线程开启。
创建方法:
public class RunnableDemo implements Runnable{
public int num=0;
@Override
public void run() {
for (;num < 100; num++) {
System.out.println(Thread.currentThread().getName()+":"+num);
}
}
}
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
//把对象转成Thread类对象进行线程开启
Thread thread1 = new Thread(runnableDemo);
thread1.setName("线程1");
Thread thread2 = new Thread(runnableDemo);
thread2.setName("线程2");
thread1.start();
thread2.start();
}
线程的运行状态:
新建状态---->就绪状态(start开启之后,cpu调用之前)----->运行状态(cpu调用)--->阻塞状态(执行过程中分配时间未使用完之前被打断)------>死亡状态(线程中的功能执行完毕)。
实现Runnable接口比继承Thread类所具有的优势
从上面的运行结果可以看出,两者的区别。
实现Runnable接口的,对于三个线程来说共享的是ThreadTest1对象的资源。
继承Thread类,三个线程都是独立的运行,线程间不共享资源。
所以可以总结出以下区别:
-
Runnable接口的话,可以避免单继承的局限性,具有较强的健壮性。
-
Runnable可以实现资源的共享,同时处理同一资源。
-
Thread类的线程间都是独立运行的,资源不共享。
-
继承Thread类不再被其他类继承(java不存在多继承)
线程锁:
多线程进行数据共享时线程不安全,数据无法进行统一,会有重复数据产生
锁:synchronized (){}代码块
-
可以将代码放入代码块中,保护线程安全
-
可以直接将synchronized写在方法的返回值前,与synchronized {}代码块有相同的作用,且不需要再创建对象
-
可以直接在()里面写入当前类.class,指定当前类为锁的对象
1、使用synchronized (){}代码块保护线程安全实现买票操作:
public class LockRunnableDemo implements Runnable{
public int num = 1000;
public Object obj = new Object();
/**
* 使用锁保护线程安全实现买票操作
*/
@Override
public void run() {
while (true){
synchronized (obj){
//循环卖票
if (num<1){ //票卖完
return;
}
System.out.println(Thread.currentThread().getName()+"剩余票量:"+num+"张");
num--;
}
}
}
}
public static void main(String[] args) {
LockRunnableDemo ticket = new LockRunnableDemo();
Thread thread1 = new Thread(ticket);
thread1.setName("1号窗口");
Thread thread2 = new Thread(ticket);
thread2.setName("2号窗口");
Thread thread3 = new Thread(ticket);
thread3.setName("3号窗口");
//开启线程
thread1.start();
thread2.start();
thread3.start();
}
2、使用synchronized直接写在返回值前 实现保护线程安全
直接将synchronized写在方法的返回值前,与synchronized {}代码块有相同的作用,且不需要再创建对象
public synchronized void test(){ // 使用 this
while (true) {
if (num < 1) {
return;
}
System.out.println(Thread.currentThread().getName() + "报:剩余" + num + "张票,开始售卖一张");
num--;
}
}
3、使用类的反射对象作为锁的对象 实现保护线程安全
直接在()里面写入当前类.class,指定当前类为锁的对象
public class ThreadDemo2 implements Runnable{
public int num = 100;
@Override
public void run() {
test();
}
public void test(){ // 使用 this
while (true) {
synchronized (ThreadDemo2.class) {
if (num < 1) {
return;
}
System.out.println(Thread.currentThread().getName() + "报:剩余" + num + "张票,开始售卖一张");
num--;
}
}
}
}
Lock锁:
使用Lock锁
java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
把需要锁维护线程安全的代码
通过 Thread对象.lock()方法添加锁
通过 Thread对象.unlock()释放锁
lock使用步骤:
ReentrantLock是lock的一个实现类 通过此类的无参构造方法就可以创建lock
-
在成员位置创建一个ReentrantLock对象
-
在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
-
在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
创建线程的类代码示例:
public class ThreadDemo3 implements Runnable{
public Lock lock;
public int num = 100;
@Override
public void run() {
test();
}
public void test(){ // 使用 this
while (true) {
lock.lock();
if (num < 1) {
return;
}
System.out.println(Thread.currentThread().getName() + "报:剩余" + num + "张票,开始售卖一张");
num--;
lock.unlock();
}
}
}
Main方法中代码示例:
public static void main(String[] args) {
ThreadDemo3 t = new ThreadDemo3();
//lock锁
//创建一个ReentrantLock对象
t.lock = new ReentrantLock();
Thread thread = new Thread(t);
thread.setName("白晶晶3");
Thread thread1 = new Thread(t);
thread1.setName("朱丝丝3");
thread.start();
thread1.start();
}
在多个线程对同一个对象使用时,lock锁时需要创建同一个锁的对象
使用lock锁的优势:
可以在不同的线程类中使用数据共享时 可以使用同一个lock锁 保护线程的安全
注意 此时使用的数据为引用数据类型
(想要多个线程中使用同一个数据,必须使用引用数据类型,通过引用地址,指向同一个数据对象)
例如:
Demo4线程类:
public class ThreadDemo4 extends Thread{
public Lock lock;
public NumberDemo nd;
@Override
public void run() {
while (true){
lock.lock();
if(nd.num<1){
lock.unlock();
return;
}
System.out.println(getName()+":数字为:"+nd.num);
nd.num--;
lock.unlock();
}
}
}
Demo5线程类:
public class ThreadDemo5 extends Thread{
public Lock lock;
public NumberDemo nd;
@Override
public void run() {
while (true){
lock.lock();
if(nd.num<1){
lock.unlock();
return;
}
System.out.println(getName()+":数字为:"+nd.num);
nd.num-=2;
lock.unlock();
}
}
}
Main方法:
public static void main(String[] args) {
NumberDemo numberDemo = new NumberDemo();
numberDemo.num=500;
Lock lock = new ReentrantLock();
ThreadDemo4 t = new ThreadDemo4();
t.nd = numberDemo;
t.lock=lock;
/*
ThreadDemo4 t2 = new ThreadDemo4();
t2.nd = numberDemo;
t2.lock=lock;
ThreadDemo4 t3 = new ThreadDemo4();
t3.nd = numberDemo;
t3.lock=lock;*/
ThreadDemo5 t5 = new ThreadDemo5();
t5.nd = numberDemo;
t5.lock=lock;
t.start();
/* t2.start();
t3.start();*/
t5.start();
}
死锁:
当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他同步。这时会引发一种现象:程序出现无限等待,这种现象我们称为死锁
简单理解:指两个线程或多个线程相互持有对方所需要的资源,导致线程都处于等待状态,无法往下执行,这就是死锁!
产生的原因:
-
多个线程中都使用了多个相同的对象作为锁
-
多个线程中都使用了锁中套锁的形式,且锁的使用顺序不同
代码示例:
public class SiSuoDemo1 extends Thread{
public Object obj1;
public Object obj2;
@Override
public void run() {
synchronized (obj1){
System.out.println(getName()+":获取锁1");
synchronized (obj2){
System.out.println(getName()+":获取锁2");
}
System.out.println(getName()+"获取锁");
}
}
}
public class SiSuoDemo2 extends Thread{
public Object obj1;
public Object obj2;
@Override
public void run() {
synchronized (obj2){
System.out.println(getName()+":获取锁2");
synchronized (obj1){
System.out.println(getName()+":获取锁1");
}
System.out.println(getName()+"获取锁");
}
}
}
public static void main(String[] args) {
//创建对象作为锁
Object o = new Object();
Object o1 = new Object();
//创建线程类对象
SiSuoDemo1 s1 = new SiSuoDemo1();
s1.obj1=o;
s1.obj2=o1;
SiSuoDemo2 s2 = new SiSuoDemo2();
s2.obj1=o;
s2.obj2=o1;
s1.start();
s2.start();
}
解决方式:
死锁的出现主要是因为同步中嵌套同步了,我们只需要保证不让它们进行嵌套即可解决死锁的出现
-
避免锁中套锁进行锁的使用 (最关键的避免死锁的方式)
-
如果套锁,需要保证多个线程中的锁有所不同,或者使用锁的顺序保持一致