线程学习笔记
一些相关概念
程序
程序:完成特定任务,用某种编程语言所写的一组指令的集合。
进程
进程:进程是正在运行的程序,有自己的产生、存在和消亡的过程。
线程
线程:由程序创建的、是进程的一个实体,一个进程可以有多个线程。
并发与并行
并发
并发:同一时刻,多个任务交替执行。即,一个处理器同时处理多个任务。
并行
并行:同一个时刻,多个任务同时执行。即,多个处理器处理同时处理多个不同的任务。
创建一个线程
创建线程的两种方法
可以通过继承实现了Runnable接口的Thread类实现,也可以直接实现Runnable接口实现。
Runnable接口提供的方法
场景描述
创建一个线程,每秒打印你好,打印八次后退出。
继承Thread类
继承Thread类实现代码:
public class Thread01 extends Thread {
private int count = 0;
private boolean loop = true; //设置循环条件
public void setLoop(boolean loop) {
this.loop = loop;
}
@Override
public void run() {
while (loop){
sleep( 1000 ); //延时1000ms
sayHi(); //打印hi
breakThread(); //线程跳出程序
}
}
public void sayHi(){
System.out.println("你好 "+"这是由"+Thread.currentThread().getName()+"创建的"+ (++count) );
}
public void sleep(int time){
try {
Thread.sleep( time );
} catch (InterruptedException e) {
throw new RuntimeException( e );
}
}
public void breakThread(){
if( count >= 8)
this.loop = false;
}
}
开启创建的线程
public class ThreadTest {
public static void main (String args[]){
//thread01 由继承Thread方法实现
Thread01 thread01 = new Thread01();
thread01.start();
}
控制台输出
实现Runnable接口
由于java是单继承机制,当前类继承了其他父类时,就无法继承Thread类,所以需要实现Runnable接口实现线程
实现Runnable接口的代码
public class Thread02 extends Person implements Runnable {
private int count = 0;
private boolean loop = true;
@Override
public void run() {
while (loop){
sleep( 1000 );
sayHello();
breakThread();
}
}
public void sayHello(){
System.out.println("hello "+"这是由"+Thread.currentThread().getName()+"创建的"+ (++count) );
}
public void sleep(int time){
try {
Thread.sleep( time );
} catch (InterruptedException e) {
throw new RuntimeException( e );
}
}
public void breakThread(){
if( count >= 8)
this.loop = false;
}
}
//Thread02类的父类
class Person{
}
开启创建的线程
public class ThreadTest {
public static void main (String args[]){
//thread02 由实现Runnable接口实现
Thread thread02 = new Thread(new Thread02() );
thread02.start();
}
}
控制台输出
两种方式比较
1、实现Runnable接口方法可以实现资源复用,可以如下开启多个线程,但共享一个资源
//创建一个共享的资源
Thread02 resourse = new Thread02();
//创建三个线程,共享一个资源
Thread thread1 = new Thread(resourse);
Thread thread2 = new Thread(resourse);
Thread thread3 = new Thread(resourse);
//开启线程
thread1.start();
thread2.start();
thread3.start();
2、如果当前类继承了其他父类就不能继承了Thread类,所以只能通过实现Runnable接口来实现多线程
run()方法和start()方法
run方法由Runnable接口提供,但是当我们直接调用run()方法时,只是普通调用一个方法,并没有开启线程。
//thread02 由实现Runnable接口实现
Thread thread02 = new Thread(new Thread02() );
//直接调用run()方法
thread02.run();
输出结果
可以和前两次输出结果对比,这里打印的线程名是主线程名,并未开启一个新的线程。
start()方法由Thread类提供,并最终执行start0()方法
官方源码如下:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
用售票方法引出线程安全问题
有三个售票站点,共计25张票,每次售票后打印剩余票数,当票数小于0时,停止售票。
用Runnable接口类模拟售票
模拟售票代码
public class SaleTicket implements Runnable {
private int num = 25; //25张票
private boolean loop = true; // 循环控制
public void setLoop(boolean loop) {
this.loop = loop;
}
//把业务代码放到run中
@Override
public void run() {
while (loop){
sell(); //卖票业务
delayTime();//延时1s
}
}
public void sell(){
if(num > 0)
System.out.println(Thread.currentThread().getName()+"售票完成,当前票数" + (--num));
else {
System.out.println(Thread.currentThread().getName()+"售票失败,当前票数 "+num);
setLoop( false );
}
}
public void delayTime(){
try {
Thread.sleep( 1000 );
} catch (InterruptedException e) {
throw new RuntimeException( e );
}
}
}
开启三个线程,共享一个资源
public class ThreadTest {
public static void main (String args[]){
//注定不安全
SaleTicket saleTicket = new SaleTicket();
Thread sell01 = new Thread(saleTicket,"御坂01号贩卖机");
Thread sell02 = new Thread(saleTicket,"御坂02号贩卖机");
Thread sell03 = new Thread(saleTicket,"御坂03号贩卖机");
sell01.start();
sell02.start();
sell03.start();
}
}
结果:
存在问题
存在0票且贩卖成功的情况,这是因为在线程01执行--num
前,02线程就执行了if(num > 0)
,且在线程01执行 后执行了自己的售票代码--num
,所以出现了负票的情况。
解决方法:使用关键字synchronized实现线程同步
方法1:在核心方法上加入synchronized关键字实现线程同步
public synchronized void sell(){
if(num > 0)
System.out.println(Thread.currentThread().getName()+"售票完成,当前票数" + (--num));
else {
System.out.println(Thread.currentThread().getName()+"售票失败,当前票数 "+num);
setLoop( false );
}
}
结果:
没有出现负票的情况。
方法2 在代码块上使用synchronized关键字
public /*synchronized */void sell(){
//this可以替换成任意当前共有的类。
synchronized (this){
if(num > 0)
System.out.println(Thread.currentThread().getName()+"售票完成,当前票数" + (--num));
else {
System.out.println(Thread.currentThread().getName()+"售票失败,当前票数 "+num);
setLoop( false );
}
}
}
结果同上。
注意事项
1、同步方法如果没有static修饰,则默认锁对象为this
2、同步方法如果有static修饰,则默认锁对象为当前类.class
3、当某个对象用synchronized修饰时,在同一时刻只能由一个线程访问该对象