Java中多线程原理 线程安全 线程同步 Lock锁 线程的六种状态
今日概念性知识较多,但均为日后操作的重点,加深理解。
01.第一章:多线程_回顾实现线程的方式一:
1).定义一个线程类,继承自Thread,重写run()方法;
class MyThread extends Thread{
public void run(){
...
}
}
2).启动线程
–|创建我们定义线程类的对象;
–|调用对象的start()方法启动线程;
main(){
MyThread t = new MyThread();
t.start();
// t.run();//只是普通的方法调用
}
02.第一章:多线程_多线程的运行原理:
03.第一章:多线程_Thread类的常用方法_构造方法:
1).构造方法:
–|Thread():无参构造方法
–|Thread(String name):使用一个线程名称构造一个线程对象
每个线程都有一个默认的线程名:Thread-索引值
通过getName()方法可以获取线程名称
–|Thread(Runnable run):使用一个Runnable的实现类构造一个线程对象;
在“创建线程的方式二”中演示
–|Thread(Runnable run,String name):使用一个Runnable和一个线程名称构造一个线程对象。
03.第一章:多线程_Thread类的常用方法_成员方法:
1).public String getName():获取线程的名称;
2).public static void sleep(long millis):让当前线程休眠指定的毫秒值;
public class MyThread extends Thread {
@Override
public void run() {
for (; ; ) {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = sdf.format(date);
System.out.println(str);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3).public static Thread currentThread():获取当前的线程对象;
public class Demo {
public static void main(String[] args) {
//主线程也是一个线程,是由JVM来启动的
//主线程也有线程名
//Thread.currentThread()可以获取当前的线程对象,然后调用getName()就能获取线程名称
for (int k = 0; k < 100; k++) {
System.out.println(Thread.currentThread().getName() + " k = " + k);
}
}
}
04.第一章:多线程_制作线程的方式二_Runnable接口:
1).定义一个子类,实现Runnable接口,重写run()方法;
/*
注意:实现Runnable后,我们的类--不是一个线程类
后期必须要委托给一个线程类Thread去执行
*/
public class MyRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " i = " + i);
}
}
}
2).启动线程:
1).创建一个自定义Runnable子类对象;
2).创建一个Thread对象,并将自定义对象作为参数传给Thread的构造方法;
3).调用Thread的start()方法启动线程。它内部会去调用我们自定义对象内部的run()方法。
示例代码:
public class Demo {
public static void main(String[] args) {
MyRun myRun = new MyRun();
Thread t1 = new Thread(myRun,"章子怡");
Thread t2 = new Thread(myRun,"汪峰");
t1.start();
t2.start();
}
}
05.第一章:多线程_两种实现方式的区别:
1).第一种:继承自Thread.由于Java单继承的限制,对于子类形成了制约。
2).第二种:实现接口Runnable。子类可以同时实现多个接口【建议使用】
06.第一章:匿名内部类的方式实现线程:
1).匿名内部类的格式:
new 父类/父接口名(){
//子类的类体
}
2).实现线程的方式一,使用匿名内部类的方式:
new Thread(){//Thread的子类类体}.start();
示例代码:
public static void main(String[] args) {
//Thread t = new Thread的匿名子类对象();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("i = " + i);
}
}
}.start();
//主线程继续
for (int k = 0; k < 100; k++) {
System.out.println("k = " + k);
}
}
2).实现线程的方式二,使用匿名内部类的方式:
new Thread(Runnable子类对象).start();
示例代码:
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("i = " + i);
}
}
}).start();
07.第一章:多线程安全性问题:
1).多线程安全性问题产生的原因:多个线程同时访问同一个变量;
2).示意图:
3).代码:
1.定义一个类,实现Runnable接口:
/*
注意:实现Runnable后,我们的类--不是一个线程类
后期必须要委托给一个线程类Thread去执行
*/
public class MyRun implements Runnable {
int a = 100;
@Override
public void run() {//线程1,线程2
for (int i = 0; i < 100; i++) {//线程1(1,2)//线程2(1,2,3)//线程1(3)
a++;//1.将a的值100取出到运算器中;2.在运算器中计算 100 + 1 ;3.将101赋给a
}
System.out.println("线程执行完毕!");
}
}
2.测试类:
public class Demo {
public static void main(String[] args) throws InterruptedException {
MyRun myRun = new MyRun();
Thread t1 = new Thread(myRun,"章子怡");
Thread t2 = new Thread(myRun,"汪峰");
t1.start();
t2.start();
//主线程休息一秒,确保上面的两个线程执行完毕
Thread.sleep(1000);
//获取最终的结果
System.out.println(myRun.a);//正常:300
}
}
08.第二章:多线程售票模拟线程安全性问题:
1).定义Tickets类:
public class Tickets implements Runnable {
private int tickets = 100;
@Override
public void run() {
//死循环
while (true){
//判断是否有票
if (tickets > 0) {
//取一张
int t = tickets;
try {
Thread.sleep(1);//目的:让效果更明显,让其它线程运行
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() +
" 取走一张票:" + t);
}else{
System.out.println("没票了!");
break;
}
}
}
}
2).定义测试类:
public class Demo {
public static void main(String[] args) {
//1.创建一个Tickets对象
Tickets t = new Tickets();
//2.创建两个线程
Thread t1 = new Thread(t,"窗口1");
Thread t2 = new Thread(t,"窗口2");
t1.start();
t2.start();
}
}
09.第二章:同步代码块_解决多线程售票线程安全性问题:
1).格式:
synchronized(锁对象){
//同步代码
}
锁对象:可以是任何对象,但必须保证多个线程共同使用同一个锁对象。
2).示例代码:
1).修改Tickets类:
public class Tickets implements Runnable {
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if (tickets > 0) {
int t = tickets;
try {
Thread.sleep(1);//目的:让效果更明显
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() +
" 取走一张票:" + t);
}else{
System.out.println("没票了!");
break;
}
}//一个线程执行完毕,会自动释放锁
}
}
}
2).主线程类:
public class Demo {
public static void main(String[] args) {
//1.创建一个Tickets对象
Tickets t = new Tickets();
//2.创建两个线程
Thread t1 = new Thread(t,"窗口1");
Thread t2 = new Thread(t,"窗口2");
t1.start();
t2.start();
}
}
10.第二章:同步方法_解决多线程售票线程安全性问题【常用】:
1).格式:
//同步方法,使用的锁对象是:this
public synchronized int getTicket(){
//同步代码
}
2).示例代码:
修改Tickets类即可:
public class Tickets implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true){
int t = this.getTicket();
if (t > 0) {
System.out.println(Thread.currentThread().getName() +
" 取走一张票:" + t);
}else{
System.out.println("没票了!!");
break;
}
}
}
//同步方法,使用的锁对象是:this
public synchronized int getTicket() {
if (tickets > 0) {
int t = tickets;
try {
Thread.sleep(1);//目的:让效果更明显
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
return t;
} else {
return 0;
}
}
}
11.第二章:Lock锁_解决多线程售票线程安全性问题:
1).格式:
Lock l = new 某个实现类对象();
l.lock();//加锁
try {
//同步代码
} finally {
l.unlock(); //解锁
}
2).示例代码:
public class Tickets implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();//加锁
try {
if (tickets > 0) {
int t = tickets;
try {
Thread.sleep(1);//目的:让效果更明显
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + " 取走一张票:" + t);
} else {
System.out.println("没票了!");
break;
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//解锁
}
}
}
}
12.第三章:线程状态:
13.第三章:线程状态_无限等待_wait方法:
public class Demo {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
new Thread(){
@Override
public void run() {
synchronized (obj) {
for (int i = 0; i < 20; i++) {
System.out.println("i = " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i == 10) {
//让当前的线程挂起,等待被唤醒
try {
System.out.println("我开始等待...");
obj.wait();//会立即释放obj锁
System.out.println("我醒了,开始干活了....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}.start();
//开启一个新线程
new Thread(){
@Override
public void run() {
//获取obj锁对象
System.out.println("第二个线程开始执行...");
synchronized (obj){
//唤醒所有在obj锁上等待线程.
System.out.println("第二个线程开始唤醒....");
obj.notifyAll();//唤醒后,
}//唤醒后,会释放锁
}
}.start();
}
}
14.第三章:线程状态_无限等待_包子铺案例:
=======================================================================
学习目标总结:
01.能够描述Java中多线程运行原理
每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈
02.能够使用继承类的方式创建多线程
class MyThread extends Thread{
public void run(){
//…
}
}
main(){
MyThread t = new MyThread();
t.start();
t.run();//普通的方法调用
t.start();//抛异常
}
03.能够使用实现接口的方式创建多线程
class MyRun implements Runnable{
public void run(){
//…
}
}
main(){
MyRun myRun = new MyRun();
Thread t = new Thread(myRun);
t.start();
}
04.能够说出实现接口方式的好处
1).解决单继承的限制。
05.能够解释安全问题的出现的原因
1).多个线程同时访问同一个变量;
06.能够使用同步代码块解决线程安全问题
synchronized(锁对象){
//同步代码
}
07.能够使用同步方法解决线程安全问题
public synchronized void show(){
//同步代码
}
08.能够说出线程6个状态的名称
1.新建
2.可运行
3.计时等待
4.锁阻塞
5.无限等待
6.被终止
09.能够理解等待唤醒案例
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
new Thread(){
@Override
public void run() {
synchronized (obj) {
for (int i = 0; i < 20; i++) {
System.out.println("i = " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i == 10) {
//让当前的线程挂起,等待被唤醒
try {
System.out.println("我开始等待...");
obj.wait();//会立即释放obj锁
System.out.println("我醒了,开始干活了....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}.start();
//开启一个新线程
new Thread(){
@Override
public void run() {
//获取obj锁对象
System.out.println("第二个线程开始执行...");
synchronized (obj){
//唤醒所有在obj锁上等待线程.
System.out.println("第二个线程开始唤醒....");
obj.notifyAll();//唤醒后,
}//唤醒后,会释放锁
}
}.start();
}