一、线程与进程
- 进程:内存中正在运行的一个应用程序。
- 线程:进程中的一个执行流程。
- 多线程:进程中有两个或以上的线程在轮询的执行。轮询的速度特别快,人感觉不到,所以多个线程就像并行执行一样。
二、线程的状态
1.线程的5种状态
- New(新建)
- Runnable(就绪)
- Running(运行)
- Blocked(阻塞)
- Dead(死亡)
2.线程状态图解
3.线程生命周期
- 新建:创建了一个线程对象,比如 Thread thread = new Thread();
- 就绪:等待CPU调用执行,比如 thread.start();
- 运行:CPU调用了处于就绪状态的一个线程,即执行了run();方法。
- 阻塞:处于运行状态的线程,由于某种原因暂停了CPU的调用执行。
- sleep() 和 join()阻塞:
- 线程A调用sleep()方法,则线程A处于阻塞状态,等待超时才能恢复到就绪状态。
- 线程A调用线程B的join()方法,则线程A等待线程B执行完毕再恢复到就绪状态。
- 同步阻塞
- wait() 阻塞
- sleep() 和 join()阻塞:
- 死亡:线程正常结束或异常退出。
三、创建线程的方式
1.主线程
- 当Java程序启动时执行。
- 主线程是产生其他子线程的线程。
- 主线程的任务就是主方法。
- 启动一个Java程序至少需要启动几个线程?两个,主线程和自动垃圾回收线程。
2.创建线程的两种方式:继承Thread和实现Runnable
2.1.继承Thread
示例:
class MyThread extends Thread{
MyThread(){}
MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 4; i++) {
System.out.println(Thread.currentThread());
}
}
}
public class TestThread {
public static void main(String[] args) {
//创建了一个子线程
MyThread t = new MyThread("线程一");
//启动线程
t.start();
//运行时异常IllegalThreadStateException,start()方法只能调用异常,也就是子线程只能启动一次
// t.start();
//不能这样启动子线程。因为线程是main,这是主线程里面的任务,跟子线程没有关系
// t.run();
}
}
2.2.实现Runnable
示例:
class ThreadDemo implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class TestThread2 {
public static void main(String[] args) {
//注意:这不是一个线程对象
ThreadDemo demo = new ThreadDemo();
//可以给线程起名
Thread t =new Thread(demo,"张三");
//启动线程时,就能调用demo对象中的run()方法了。
t.start();
}
}
2.3.两种创建方式的区别
- 继承Thread类后就是线程类,而实现Runnable接口不是线程类;
- 继承Thread类的所有功能,实现接口只有run()方法;
- 实现接口利于资源共享。
四、线程的优先级
1.什么是线程的优先级?有什么用处?取值范围是什么?
- 理论上,优先级高的线程比优先级低的线程获得更多的CPU时间,相等优先级的线程有同等的权利使用CPU。
- 实际上,线程获得CPU的时间通常由包括优先级在内的多个因素决定。
- 线程优先级从低到高的范围是1-10。
2.如何设置和查看线程的优先级?
示例:
class SubThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ":玩儿游戏");
}
}
}
public class TestSubThread2 {
public static void main(String[] args) {
SubThread2 th = new SubThread2();
Thread zhangsan = new Thread(th,"zhangsan");
Thread lisi = new Thread(th,"lisi");
// zhangsan.setPriority(1);
// lisi.setPriority(10);
zhangsan.setPriority(Thread.MAX_PRIORITY);
lisi.setPriority(Thread.MIN_PRIORITY);
zhangsan.start();
lisi.start();
System.out.println(zhangsan.getPriority());
System.out.println(lisi.getPriority());
}
}
五、线程相关方法
常见方法:
- sleep():阻塞线程,参数是long类型,表示当前线程被阻塞的毫秒数。在当前线程中执行Thread.sleep(2000);代码即可。
- join():线程A调用线程B的join()方法,则线程A等待线程B执行完毕再恢复到就绪状态。
- wait():放弃CPU时间,放弃锁,进入阻塞状态。调用方式:在当前线程中直接执行 waite()即可。可以带参或不带参。
- notify()/notifyAll():唤醒通过waite()阻塞的线程,进入同步阻塞状态。调用方式:在当前线程中直接执行notify()即可。
- interrupt():线程A中调用线程B的interrupt()方法,那中么线程B被断进入到异常处理流程,但前提是线程B必须处于sleep或join状态,否则不会引发这个异常。
- yield():线程让步,让同等优先级或高优先级的线程获取CPU时间。在当前线程中执行Thread.yield();代码即可。
sleep() 与 wait() 的区别:
- sleep() 必须指定参数,wait()参数是可选的;
- sleep()放弃了CPU的时间但不放弃锁,wait()放弃了CPU的时间也放弃了锁。
示例:
class MyThread3 implements Runnable{
private int i = 10;
@Override
public void run() {
synchronized(this){
String threadName = Thread.currentThread().getName();
for (; i >= 0; i-- ){
if (i == 5 && threadName.equals("t1")) {
try {
// Thread.sleep(1000);
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(threadName + ":" + i);
if (i == 0) {
System.out.println("我醒了");
notify();
}
}
}
}
}
public class TestSleepAndWait {
public static void main(String[] args) {
MyThread3 th = new MyThread3();
Thread t1 = new Thread(th, "t1");
Thread t2 = new Thread(th, "t2");
t1.start();
t2.start();
}
}
六、线程的同步
1.什么是线程同步?
- 当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用。达到此目的的过程叫做同步(synchronized)
- 在资源共享的情况下,线程A访问某对象的同步资源获得同步锁,那么其他线程就不能再访问此对象的同步同步资源了,但是可以访问其他对象的资源方法,或者访问此对象的非同步资源。
- 当一个线程需要锁定,它必须进入管程。管程是使用资源独占的一种权利
2.为什么使用同步?
保证对共享资源的读写一致性。
3.如何使用线程同步?
3.1.synchronized关键词
语法:
- 同步方法:synchronized 方法名(){ }
- 同步代码块:synchronized(对象){ //同步块 }
- 以上二者方式锁的都是对象。
获得锁和释放锁的标志:(没有明确的获得锁或释放锁的标志,但是可以推断出下面这些情况!)
- 获得锁:当线程执行同步代码块或同步方法中的代码时,需要获得对象锁。
- 释放锁:
- 同步代码块或同步方法中的代码执行完毕;
- return,break结束了方法;
- 同步代码块或同步方法中出现了没有处理的异常;
- 线程执行了wait()方法。
示例:同一银行账户存钱
class Bank implements Runnable{
private int money;
//同步方法
/*private synchronized void setMoney(){
this.money += 100;
System.out.println(Thread.currentThread().getName() + "存了100元,余额" + this.money + "元");
}*/
private void setMoney(){
this.money += 100;
System.out.println(Thread.currentThread().getName() + "存了100元,余额" + this.money + "元");
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
//同步代码块
synchronized(this){
setMoney();
}
}
}
}
public class TestBank {
public static void main(String[] args) {
Bank bank = new Bank();
Thread zhangsan = new Thread(bank,"张三");
Thread lisi = new Thread(bank,"李四");
zhangsan.start();
lisi.start();
}
}
3.2.ReentrantLock锁(公平锁)
语法:
try{
//加锁lock()
}finally{
//释放锁unlock()
}
优势:
- 可以显示的加锁和释放锁。
- 是一种公平锁,CPU会把锁让给等待时间最长的那个线程。
示例:多线程实现5个小朋友报数,每间隔一秒报数一次。
import java.util.concurrent.locks.ReentrantLock;
class Person implements Runnable{
private int num = 1;
//创建公平锁对象
private ReentrantLock lock = new ReentrantLock();
private void askNum(String name){
try {
//启动锁功能
lock.lock();
System.out.println(name + "\t" + num++);
} finally {
//finally保证一定释放锁
lock.unlock();
}
}
@Override
public void run() {
askNum(Thread.currentThread().getName());
}
}
public class TestAskNum {
public static void main(String[] args) {
Person p = new Person();
Thread t1 = new Thread(p, "郭靖");
Thread t2 = new Thread(p, "杨康");
Thread t3 = new Thread(p, "欧阳克");
Thread t4 = new Thread(p, "周伯通");
Thread t5 = new Thread(p, "黄蓉");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
4.线程同步的特点
- 每个对象只有一个锁(lock)与之关联。
- 实现同步要很大的系统开销作为代价,甚至可能造成死锁,所以要尽量避免无谓的同步控制。
七、线程死锁
1.什么是死锁?
多个线程进入到了循环等待的状态,就造成了死锁。
2.死锁的特点?
- 死锁是很难调试的错误,只有两个线程的时间段刚好符合时才能发生。
- 我们在编写多线程并含有同步方法调用的程序中要格外小心,避免死锁发生。
3.如何写一个死锁?
示例:两个线程相互拿着对方需要的锁,你不给我,我就没法释放你需要的锁。
class ZS{
public void say(){
System.out.println("张三:你给我书,我就给你画");
}
public void get(){
System.out.println("张三获得了书");
}
}
class LS{
public void say(){
System.out.println("李四:你给我画,我就给你书");
}
public void get(){
System.out.println("李四获得了画");
}
}
class MyThread2 extends Thread{
static ZS zhangsan = new ZS();
static LS lisi = new LS();
boolean tag = false;
@Override
public void run() {
if(tag == true){
//相互拿着对方需要的锁,你不给我,我就没法释放你需要的锁
synchronized(zhangsan){
zhangsan.say();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lisi) {
zhangsan.get();
}
}
} else {
//相互拿着对方需要的锁,你不给我,我就没法释放你需要的锁
synchronized(lisi){
lisi.say();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(zhangsan){
lisi.get();
}
}
}
}
}
public class TestLock {
public static void main(String[] args) {
MyThread2 zhangsan = new MyThread2();
zhangsan.tag = true;
MyThread2 lisi = new MyThread2();
zhangsan.start();
lisi.start();
}
}
八、线程协作操作
1.多线程
示例:
class SubThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ":玩儿游戏");
}
}
}
public class TestSubThread1 {
public static void main(String[] args) throws InterruptedException {
//注意:这不是一个线程对象
SubThread1 sub1 = new SubThread1();
//创建线程对象
Thread zhangsan = new Thread(sub1, "张三");
Thread lisi = new Thread(sub1, "李四");
zhangsan.start();
lisi.start();
//毫秒 等待足够长的时间
// Thread.sleep(1000);
//线程是否运行,是true
/*if (zhangsan.isAlive() || lisi.isAlive()) {
Thread.sleep(10);
}*/
//join() 让调用该方法的线程先执行,执行完了主线程再进入就绪状态。
zhangsan.join();
lisi.join();
System.out.println("程序结束!");
}
}
2.生产与销售模式
示例:实现生产包子和销售包子
class QingFeng {
private int count;
private boolean tag = false;
synchronized public void put(int count){
//true
if (tag == true) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.count = count;
System.out.println("生产:" + this.count);
tag = true;
notify();
}
synchronized public void get(){
//没有包子
if (tag == false) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费:" + this.count);
tag = false;
notify();
}
}
//生产
class Productor implements Runnable{
private QingFeng qingfeng;
public Productor(){}
public Productor(QingFeng qingfeng){this.qingfeng = qingfeng;}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
qingfeng.put(i);
}
}
}
//销售
class Customer implements Runnable{
private QingFeng qingfeng;
public Customer(){}
public Customer(QingFeng qingfeng){this.qingfeng = qingfeng;}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
qingfeng.get();
}
}
}
public class TestQingFeng {
public static void main(String[] args) {
QingFeng qf = new QingFeng();
Productor p = new Productor(qf);
Customer c = new Customer(qf);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}