2.2初始线程:线程的基本操作
进行java并发程序设计的第一步, 就必须要知道java中为线程操作提供了那些API。比如创建,启动,终止。因为并行操作比串行要复杂,所以围绕这些常用的接口,总有一大堆坑等着你去踩。
2.2.1 新建线程
新建线程很简单,只需要用new关键字创建一个线程对象,并且将它start()起来就可以
Thread t1 = new Thread()
t1.start()
那线程start()以后,要做什么才是问题的关键,线程Thread,有一个run()方法,start()方法会新启动一个线程,让这个线程去执行run()方法。
Thread tl=new Thread();
tl.run();
注意::不要用 run()方法来开启新线程 它只会在当前线中串行执行 run()方法中的代码。
start()方法开启线程, run()方法来定义开启这个线程后这个线程去执行的工作
在默认的情况下,线程Thread的run()方法什么都没有做,因此,这个线程一启动就结束了,如果你想让线程做点什么,就必须重写run()方法,把你的“任务”加进去
Thread t1 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是线程t1 执行的 第"+i+"个");
}
}
};
t1.start();
上面这个代码使用了匿名内部类, 重写了run方法,循环打印,如果没有特别的需要,都可以通过继承线程Thread,重写run()方法来自定义线程,继承本身也是宝贵的资源,所以,我们使用Runnable接口来实现操作,Runnable是单方法接口,只有一个run()方法。
public interface Runnable {
public abstract void run();
}
Thread 类有 个非常重 的构造方法:
public Thread(Runnable target)
它传入一 Runnable 口的 实例,在 tart() 方法调用时,新的线程就会执行Runnable.run()方法 。实际上,默认的 Thr ad.run()方法就是这么做的:
public class ThreadDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是t1 列的 第"+i+"个");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new ThreadDemo());
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println(i);
}
}
}
上述代码实现了 Runnabl 接口,并将该实例传入线程 Thread 中。这样避免重写 Thread.run()方法,单纯使用接口来定义线程 Thread 。
如何正常的关闭一个线程,查阅JDK,线程Thread提供了一个stop()的方法,如果你使用 sto () 方法,就可以立即将 个钱程终,但如果你使用工具写代码 ,就会发现stop() 方法是一个被标注为废弃的方法。 也就是说,在将来, JDK 可能就会移除该方法。
注:废弃的原因是 stop()方法过于暴力,强行把执行到 半的线程终止,可能会引起 些数据不一致的问题。
当强行使用stop() 去终止线程的时候造成数据不一致,就会造成数据永久的破坏,后果不堪设想。一般单线程不会出现这个问题,但是如果在并行程序中, 如果考虑不全面,就会出现这种情况。
Thread.stop()方法在结束线程时,会直接终止线程,并立即释放这个线程所持有的锁, 而这些锁恰恰是用来维持对象一致性的 如果此时 写线程写入数据正写到一半, 并强行 终止,那么对象就会被写坏,同时,由于锁己经被释放,另外一个等待该锁的读线程就会读到一个不一致的对象。
首先,对象 持有 ID NAME 两个字段 假设当 ID 等于 NAME 时表示对象是一致,否则表示对象出错,写线程总是会把ID和NAME写成相同的值,初始值都是0,当写线程在写对象时,读线程是无法获得锁,因此必须得等待,所以读线程是看不见一个写了一半的对象的,当写线程写完ID后被stop就会造成ID和NAMe的值不一样。而被终止的写线程简单的将锁释放,读线程争夺到锁后,读取数据,于是出现了错误值
package com.study.thread.thread;
/**
* @author yd
* @version 1.0
* @date 2022/3/28 22:17
*/
public class ThreadStopDemo {
public static User user = new User();
public static class User{
private int id;
private String name;
public User(){
id=0;
name="0";
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) {this.name = name;}
@Override
public String toString() {
return "User{" +"id=" + id +", name='" + name + '\'' +'}';
}
}
public static class ChangeThread extends Thread{
@Override
public void run() {
while (true){
synchronized (user){
int v = (int) (System.currentTimeMillis()/1000);
user.setId(v);
try {
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
user.setName(String.valueOf(v));
}
Thread.yield();
}
}
}
public static class ReadObjectThread extends Thread{
@Override
public void run() {
while (true){
synchronized (user){
if (user.getId()!=Integer.parseInt(user.getName())){
System.out.println(user.toString());
}
}
Thread.yield();
}
}
}
public static void main(String[] args) throws InterruptedException {
new ReadObjectThread().start();
while (true){
ChangeThread changeThread = new ChangeThread();
changeThread.start();
Thread.sleep(150);
changeThread.stop();
}
}
}
输出结果。
User{id=1648648685, name='1648648646'}
User{id=1648648685, name='1648648646'}
那么问题来了,咱咋安全的停止一个线程呢, 我们自己决定线程在何时退出就可以了。
public static class ChangeThread extends Thread{
private boolean stopme=false;
public void stopMe(){
stopme=true;
}
@Override
public void run() {
while (true){
if (stopme){
System.out.println("你想让我退出? 我就退出");
break;
}
synchronized (user){
int v = (int) (System.currentTimeMillis()/1000);
user.setId(v);
try {
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
user.setName(String.valueOf(v));
}
Thread.yield();
}
}
}
简单来说就是不要在写id和NAME中间停, 要么ID和NAME都写,或者不写的时候,停掉线程。