目录
1.进程和线程的区别
- 每个进程都有独立的代码和数据空间,进程的切换会有很大的开销
- 同一类线程共享代码和数据空间,每个线程有独立运行的栈和程序计数器,线程切换的开销小
2.多线程的实现
(1) 创建线程类
继承Thread类或实现Runnable接口
(2) 通过Thread类构造器来创建线程对象
Thread( )
Thread(Runnable target)
(3) 通过start()方法激活线程对象
例子:
(1)创建继承Thread的类
package test;
public class ThreadTest extends Thread{//基层Thread类
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i + " ");
}
}
}
(2)创建实现Runnable接口的类
package test;
public class RunnableTest implements Runnable{//实现Runnable接口
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i + " ");
}
}
}
(3)测试
package test;
public class Test {
public static void main(String[] args) {
ThreadTest test1 = new ThreadTest();
test1.start();//使线程就进入就绪状态
test1.setName("test1");//设置test1线程的名字
RunnableTest runnable = new RunnableTest();
Thread test2 = new Thread(runnable);
test2.setName("test2");//设置test2线程的名字
test2.start();
}
}
3.多线程状态及生命周期
4.线程中的主要方法
例子:
ThreadTest类:
package test;
public class ThreadTest extends Thread{//基层Thread类
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i + " ");
}
}
}
RunnableTest类:
package test;
public class RunnableTest implements Runnable{//实现Runnable接口
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i + " ");
}
}
}
Test类:
package test;
public class Test {
public static void main(String[] args) {
ThreadTest test1 = new ThreadTest();
test1.start();//使线程就进入就绪状态
test1.setName("test1");
try {
test1.join();//等待test1线程执行完毕,再执行其它线程
} catch (InterruptedException e) {
e.printStackTrace();
}
RunnableTest runnable = new RunnableTest();
Thread test2 = new Thread(runnable);
test2.setName("test2");
test2.start();
Thread.yield();//让出CPU,当前线程进入就绪队列等待调度
RunnableTest runnable2 = new RunnableTest();
Thread test3 = new Thread(runnable2);
test3.setName("test3");
test3.start();
try {
Thread.sleep(50);//将当前线程睡眠50毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
5.线程的优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照线程的优先级来决定应调度哪个线程来执行
- Java线程的优先级用1~10的整数来表示,越小则优先级越低
- 但是Java的优先级是高度依赖于操作系统的实现的
Thread类的三个常量,表示常用的线程优先级:
- Thread.MIN_PRIORITY :1
- Thread.NORM_PRIORITY:5
- Thread.MAX_PRIORITY:10
缺省时线程具有NORM_PRIORITY,通过getPriority()和setPriority()获取和设置线程优先级
package test;
public class Test {
public static void main(String[] args) {
ThreadTest test1 = new ThreadTest();
test1.start();//使线程就进入就绪状态
test1.setName("test1");
System.out.println(test1.getPriority());//5,test1线程优先级
test1.setPriority(Thread.MIN_PRIORITY);//设置test2线程优先级为1
RunnableTest runnable = new RunnableTest();
Thread test2 = new Thread(runnable);
test2.setName("test2");
test2.setPriority(Thread.MAX_PRIORITY);//设置test2线程优先级为10
test2.start();
RunnableTest runnable2 = new RunnableTest();
Thread test3 = new Thread(runnable2);
test3.setName("test3");
test3.start();
test1.setPriority(Thread.NORM_PRIORITY);//设置test3线程优先级为5
}
}
6.线程的高级操作
Object类中线程的相关方法:
①void wait()
导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll() 方法。
②void notify()
唤醒在此对象监视器上等待的单个线程。
③void notifyAll()
唤醒在此对象监视器上等待的所有线程。
例子:
①Bread类
package com.waitandnotify.test;
/**
* 面包程序: 1.如果有面包那么就销售面包 2.如果没有面包那么就生产面包
*
*
* 如果希望实现线程之间互相通知的效果: 1.线程调用的方法必须能够锁定对象的状态,可以是锁定方法,也可以是直接锁定对象 2. 有线程调用wait方法
* 让出cpu的时间片 3.有线程调用notifyAll方法,唤醒其他等待对象所得线程 4.2和3在不同的代码块中( 线程中)调用
*/
public class Bread {
private Boolean isHave = false;// 默认没有面包
// 销售面包的方法
public synchronized void sale() {
if (isHave) {
System.out.println(Thread.currentThread().getName() + "销售面包的方法sale()");
// 销售完面包没了
isHave = false;
this.notifyAll();// 虽然唤醒所有等着面包对象的锁的人,实际真正抢到时间片的线程只有一个
} else {
// 销售面包的人,没有面包销售时就等着,等生产者生产面包之后通知他
try {
System.out.println(Thread.currentThread().getName() + "在等待对象的锁");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "有面包了,完成sale方法");
}
}
// 生产面包的方法
public synchronized void produce() {
// p1 p2
if (!isHave) {
// p1 p2
System.out.println(Thread.currentThread().getName() + "生产面包的方法");
// 销售完面包没了
isHave = true;
this.notifyAll();// 虽然唤醒所有等着面包对象的锁的人,实际真正抢到时间片的线程只有一个
} else {
// 生产面包的人,没有面包销售时就等着,等着销售面包之后通知他
try {
System.out.println(Thread.currentThread().getName() + "在等待对象的锁");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "没有面包了,完成produce方法");
}
}
}
②ProductThread类
package com.waitandnotify.test;
public class ProductThread implements Runnable {
private Bread bread;
public ProductThread(Bread bread) {
this.bread = bread;
}
@Override
public void run() {
for (int i = 0; i < 200; i++) {
bread.produce();
}
}
}
③SaleThread类
package com.waitandnotify.test;
public class SaleThread implements Runnable{
private Bread bread = null;
public SaleThread(Bread bread) {
this.bread = bread;
}
@Override
public void run() {
for (int i = 0; i < 200; i++) {
bread.sale();
}
}
}
④Test测试类
package com.waitandnotify.test;
public class Test {
public static void main(String[] args) {
Bread bread = new Bread();
SaleThread sale1 = new SaleThread(bread);
ProductThread prod1 = new ProductThread(bread);
Thread s1 = new Thread(sale1);
s1.setName("销售者1");
Thread p1 = new Thread(prod1);
p1.setName("生产者1");
s1.start();
p1.start();
}
}
7.线程同步
- 有时两个或多个线程可能会试图同时访问一个资源
- 为了确保在任何时间点一个共享的资源只被一个线程使用,使用了synchronized关键字实现“同步”
- 当一个线程运行到需要同步的语句后,CPU不去执行其他线程中的、可能影响当前线程中的下一句代码的执行结果的代码块,必须等到下一句执行完后才能去执行其他线程中的相关代码块,这就是线程同步
(1)实现线程同步的两种方式
①synchronized方法
synchronized void methodA() { }
②synchronized语句
synchronized (Object) {
//要同步的语句
}
(2)注意事项
受到synchronized保护的程序代码块和方法中,要访问的对象属性必须设定为private,因为如果不设定为private,那么就可以用不同的方式来访问它,这样就达不到保护的效果了
(3)例子
Ticket类
package com.ticket.test;
public class Ticket implements Runnable {
private int number = 100;// 一共100张票
public synchronized void purchase() {// 购票
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "买了票:No." + number);
number--;
}
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
purchase();
}
}
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
t1.setName("小明");
Thread t2 = new Thread(ticket);
t2.setName("小红");
Thread t3 = new Thread(ticket);
t3.setName("小刚");
//三个人同时抢票
t1.start();
t2.start();
t3.start();
}
}
8.死锁
(1)概念
两个线程,彼此在等待对方占据的锁
(2)锁归还的几种方式
- 基本上执行完同步的程序代码后,锁就会自动归还;
- 用break语句跳出同步的语句块,不过这对于写在方法声明的synchronized没有作用;
- 遇到return语句;
- 遇到了异常;
(3)例子
Thread1类
package com.dielock.test;
public class Thread1 extends Thread{
Object o1 = null;
Object o2 = null;
public Thread1(Object o1, Object o2){
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1) {
try {
Thread.sleep(500);
synchronized (o2) {
System.out.println("o2");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread2类:
package com.dielock.test;
public class Thread2 extends Thread{
Object o1 = null;
Object o2 = null;
public Thread2(Object o1, Object o2){
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized(o2){
try {
Thread.sleep(500);
synchronized(o1){
System.out.println(o1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Test测试类:
package com.dielock.test;
public class Test {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread1 t1 = new Thread1(o1, o2);
t1.setName("t1");
t1.start();
Thread2 t2 = new Thread2(o1, o2);
t2.setName("t2");
t2.start();
}
}
9.Lock对象
如何锁对象?
- 不要锁方法
- 把进入面包房要执行的工作代码放入锁对象的代码块中
- 注意事项:如果访问对象的属性,那么属性必须是私有的
package com.ticket.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable {
private int num = 100;// 一共一百张票
private Lock lock = new ReentrantLock();//创建锁对象
@Override
public void run() {
lock.lock();//上锁
for (int i = 0; i < 10; i++) {//一人买十张
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "买了No." + num);
num--;
} else {
break;
}
}
lock.unlock();//解锁
}
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("XiaoHong");
t2.setName("XiaoLiu");
t3.setName("XiaoXin");
t1.start();
t2.start();
t3.start();
}
}
输出结果:
XiaoHong买了No.100
XiaoHong买了No.99
XiaoHong买了No.98
XiaoHong买了No.97
XiaoHong买了No.96
XiaoHong买了No.95
XiaoHong买了No.94
XiaoHong买了No.93
XiaoHong买了No.92
XiaoHong买了No.91
XiaoLiu买了No.90
XiaoLiu买了No.89
XiaoLiu买了No.88
XiaoLiu买了No.87
XiaoLiu买了No.86
XiaoLiu买了No.85
XiaoLiu买了No.84
XiaoLiu买了No.83
XiaoLiu买了No.82
XiaoLiu买了No.81
XiaoXin买了No.80
XiaoXin买了No.79
XiaoXin买了No.78
XiaoXin买了No.77
XiaoXin买了No.76
XiaoXin买了No.75
XiaoXin买了No.74
XiaoXin买了No.73
XiaoXin买了No.72
XiaoXin买了No.71