一、线程创建的两种方式
1.1继承Thread
class MyThread extends Thread{
@Override
public void run() {
}
}
//创建线程
MyThread myThread = new MyThread();
//启动线程
myThread.start();
1.2实现Runnable接口
class MyThread implements Runnable{
@Override
public void run() {
}
}
MyThread mt = new MyThread();
Thread td = new Thread(mt);
td.start();
1.3比较
- Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷
- Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况
二、卖火车票
2.1 Thread实现
public class TicketsThread {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("一号窗口");
MyThread myThread2 = new MyThread("二号窗口");
MyThread myThread3 = new MyThread("三号窗口");
myThread1.start();
myThread2.start();
myThread3.start();
}
}
class MyThread extends Thread{
private int ticketsCont = 5 ;
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
while (ticketsCont > 0) {
//有票就卖一张
ticketsCont--;
System.out.println(name+"卖了一张票,剩余票数为"+ ticketsCont);
}
}
}
运行结果
二号窗口卖了一张票,剩余票数为4
二号窗口卖了一张票,剩余票数为3
二号窗口卖了一张票,剩余票数为2
二号窗口卖了一张票,剩余票数为1
二号窗口卖了一张票,剩余票数为0
三号窗口卖了一张票,剩余票数为4
三号窗口卖了一张票,剩余票数为3
三号窗口卖了一张票,剩余票数为2
三号窗口卖了一张票,剩余票数为1
三号窗口卖了一张票,剩余票数为0
一号窗口卖了一张票,剩余票数为4
一号窗口卖了一张票,剩余票数为3
一号窗口卖了一张票,剩余票数为2
一号窗口卖了一张票,剩余票数为1
一号窗口卖了一张票,剩余票数为0
结果并不能满意。没有对票数这个多线程共同访问的数据进行同步,使得每一个线程都有自己的一个数据源。
2.2Runnable实现
public class TicketsRunnable {
public static void main(String[] args) {
MyThread mThread = new MyThread();
Thread thread1 = new Thread(mThread,"窗口1");
Thread thread2 = new Thread(mThread,"窗口2");
Thread thread3 = new Thread(mThread,"窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread implements Runnable{
private int ticketsCont = 5;
@Override
public void run() {
while (ticketsCont > 0) {
//有票就卖一张
ticketsCont--;
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数为"+ ticketsCont);
}
}
}
运行结果
窗口1卖了一张票,剩余票数为4
窗口1卖了一张票,剩余票数为3
窗口3卖了一张票,剩余票数为1
窗口2卖了一张票,剩余票数为0
窗口1卖了一张票,剩余票数为2
仍然不能满足要求,这种不符合常理的结果,没有达到预想中的4 3 2 1 0
这就是线程的交互执行导致的;举个例子:
线程1先执行卖了1张票,也即是票--1,现在票为4,但是这个线程还没没有来得及在控制台打印出剩余多少票,线程又抢到了CPU资源执行,线程2又把票--1;此时票为3,线程2输出票就为3,线程2执行完了后,线程1又再次获得CPU资源,继续把刚刚没有输出的话输出,但是此时票已经为3了,于是又输出了3。
看过多线程的小伙伴应该不难理解,这就是线程不安全,想要保证输出结果,可以使用synchronized关键字来解决
2.3 实现
public class TicketsRunnable {
public static void main(String[] args) {
MyThread mThread = new MyThread();
Thread thread1 = new Thread(mThread,"窗口1");
Thread thread2 = new Thread(mThread,"窗口2");
Thread thread3 = new Thread(mThread,"窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread implements Runnable {
private int ticketsCont = 10;
@Override
public void run() {
while (ticketsCont > 0) {
//有票就卖一张
synchronized (this) {
if (ticketsCont > 0) {
ticketsCont--;
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数为"+ ticketsCont);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
用同步锁来实现,注意在锁里也加入判断语句,不然可能会出现线程1进入了while循环,然后被抢去了线程,最后卖出了51张票。
运行结果
窗口1卖了一张票,剩余票数为9
窗口3卖了一张票,剩余票数为8
窗口3卖了一张票,剩余票数为7
窗口2卖了一张票,剩余票数为6
窗口2卖了一张票,剩余票数为5
窗口2卖了一张票,剩余票数为4
窗口3卖了一张票,剩余票数为3
窗口3卖了一张票,剩余票数为2
窗口1卖了一张票,剩余票数为1
窗口3卖了一张票,剩余票数为0
三、线程的生命周期
3.1 创建
Thread thd = new Thread();
3.2 就绪
start()被调用即进入就绪状态,线程被加入到了线程队列中,等待CPU服务,具备了运行的条件,但不一定已经开始运行了
3.3 运行
获取到了CPU服务,执行run()逻辑
3.4 阻塞
受到阻塞事件的影响,由于某种原因,让出cpu资源,如sleep()方法
3.5 终止
run()执行完毕
四、守护线程
4.1 线程分类
- 用户线程:
看得到的,主线程、连接网络的子线程等。 - 守护线程:
运行在后台,为用户线程服务。
特点:一旦所有用户线程结束运行,守护线程会随着JVM一起结束工作。
应用:数据库连接池中的监测线程 & JVM虚拟机启动后的监测线程 & 垃圾回收线程
4.2注意
- 在start()前调用方法 setDaemon(true)
- 守护线程中产生的新线程也是守护线程
- 不是所有的任务都可以分配给守护线程来执行,比如读写操作、逻辑运算。
4.3守护线程中进行读写操作
public class DaemonThreadDemo {
public static void main(String[] args) {
System.out.println("进入主线程" + Thread.currentThread().getName());
DaemonThread daemonThread = new DaemonThread();
Thread thread = new Thread(daemonThread);
thread.setDaemon(true);
thread.start();
Scanner scanner = new Scanner(System.in);
scanner.next();
System.out.println("退出主线程" + Thread.currentThread().getName());
}
}
class DaemonThread implements Runnable {
public void run() {
System.out.println("程序进入了守护线程" + Thread.currentThread().getName());
try {
writeToFile();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("程序退出了守护线程" + Thread.currentThread().getName());
}
private void writeToFile() throws Exception {
File file = new File("G:"+ File.separator+ "demo.txt");
//true 追加操作,不是覆盖操作
OutputStream os = new FileOutputStream(file,true);
int count = 0;
while (count < 999) {
os.write(("\r\nword"+count).getBytes());
System.out.println("守护线程"+ Thread.currentThread().getName()+"向文件中写入了word"+ count++);
Thread.sleep(1000);
}
os.close();
}
}
运行结果
进入主线程main
程序进入了守护线程Thread-0
守护线程Thread-0向文件中写入了word0
守护线程Thread-0向文件中写入了word1
守护线程Thread-0向文件中写入了word2
8
退出主线程main
守护线程未正常退出