线程
线程
2019-11-1414:44:18
/**
* 程序 : 保存在硬盘上的一段可执行的静态代码的集合(是可执行文件)
* 进程 : 一段正在内存中运行中的程序(有生命周期), 进程之间不可以直接互访.
* 线程 : 一个进程中的多个子任务, 直接由CPU负责处理执行. 多线程之间可以方便的共享数据
*
* 线程启动第一种方式, 实现的方式
* 1) 写一个具体类, 实现Runnable接口, 并实现接口中的抽象方法run, 这个方法就是线程体
* 2) 创建这个具体类的对象, 并以它为实参再创建Thread类的对象, 就是线程对象
* 3) 调用线程对象的start方法, 启动线程, 具体类中的run方法就会被调用执行.
*
* 线程启动第二种方式, 继承的方式
* 1) 写一个具体类, 继承Thread类, 必须重写run方法, 因为父类中的run方法什么也没有做
* 2) 创建这个具体类对象, 相当于创建了线程对象
* 3) 调用此线程对象的start方法, 启动线程.
*
* 多线程程序的优点:
提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
提高计算机系统CPU的利用率
改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
生命周期 :
阻塞(sleep, join, wait...)
| |
| 失去CPU执行权 |
新建(new Thread()) -> 就绪(start()) <--------------- | 正常或异常结束run
---------------> 运行-----------------------> 死亡
获得CPU执行权
线程同步 : 多个线程 "同时" 修改 "同"一个数据
*/
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
* 程序 : 保存在硬盘上的一段可执行的静态代码的集合(是可执行文件)
* 进程 : 一段正在内存中运行中的程序(有生命周期), 进程之间不可以直接互访.
* 线程 : 一个进程中的多个子任务, 直接由CPU负责处理执行. 多线程之间可以方便的共享数据
*
* 线程启动第一种方式, 实现的方式
* 1) 写一个具体类, 实现Runnable接口, 并实现接口中的抽象方法run, 这个方法就是线程体
* 2) 创建这个具体类的对象, 并以它为实参再创建Thread类的对象, 就是线程对象
* 3) 调用线程对象的start方法, 启动线程, 具体类中的run方法就会被调用执行.
*
* 线程启动第二种方式, 继承的方式
* 1) 写一个具体类, 继承Thread类, 必须重写run方法, 因为父类中的run方法什么也没有做
* 2) 创建这个具体类对象, 相当于创建了线程对象
* 3) 调用此线程对象的start方法, 启动线程.
*
* 多线程程序的优点:
提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
提高计算机系统CPU的利用率
改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
生命周期 :
阻塞(sleep, join, wait...)
| |
| 失去CPU执行权 |
新建(new Thread()) -> 就绪(start()) <--------------- | 正常或异常结束run
---------------> 运行-----------------------> 死亡
获得CPU执行权
线程同步 : 多个线程 "同时" 修改 "同"一个数据
*/
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Thread myThread = new MyThread(); // 相当于建栈
myThread.start(); // 激活栈, 把run压入
try {
myThread.join(); // 主线程阻塞, 子线程执行, 子线程执行完, 主线程才继续.
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
public static void main(String[] args) {
Thread myThread = new MyThread(); // 相当于建栈
myThread.start(); // 激活栈, 把run压入
try {
myThread.join(); // 主线程阻塞, 子线程执行, 子线程执行完, 主线程才继续.
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
一、创建并启动一个线程的方法
1)实现接口的方式
① 写一个具体类, 实现Runnable接口, 并实现其中的run方法,这个run方法就是线程的入口方法
② 创建这个类的对象, 并以这个对象为实参, 创建Thread类的对象
③ 调用Thread对象的start()方法 启动线程
public class ExerRunner implements Runnable { //1 创建一个子线程,在线程中输出1-100之间的偶数,主线程输出1-100之间的奇数。 private int count = 1; @Override public void run() { for (; count < 100; count++) { if (count % 2 == 0) { System.out.println(Thread.currentThread().getName() + " : " + count); } } } } public class ExerRunnerTest { public static void main(String[] args) { //1 创建一个子线程,在线程中输出1-100之间的偶数,主线程输出1-100之间的奇数。 Runnable exerRunner = new ExerRunner(); Thread thread = new Thread(exerRunner); thread.start(); for (int i = 0; i < 100; i++) { if (i % 2 != 0) { System.out.println(Thread.currentThread().getName() + " : " + i); } } } }
二、同时 创建俩个 子线程
public class HelloRunner implements Runnable { private int i; @Override public void run() { for (i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "** " + i); // 哪个栈压入此方法, 就获取哪个栈 } } } public class HelloRunner2 implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "## " + i); } } } public class HelloRunnerTest { public static void main(String[] args) { // 1) 写一个具体类, 实现Runnable接口, 并实现接口中的抽象方法run, 这个方法就是线程体 //* 2) 创建这个具体类的对象, 并以它为实参再创建Thread类的对象, 就是线程对象 Runnable runner = new HelloRunner(); Thread thread = new Thread(runner); // 执行此操作时在内部创建一个栈. thread.setName("子线程"); //* 3) 调用线程对象的start方法, 启动线程, 具体类中的run方法就会被调用执行. thread.start(); // 两个动作, 1) 激活栈, 2) 把run压入栈底, 马上结束返回 //thread.run(); run只是普通方法调用 Thread thread2 = new Thread(runner); thread2.start(); //Thread.currentThread().setName("主线程"); // 启动另一个子线程 Runnable helloRunner2 = new HelloRunner2(); Thread thread3 = new Thread(helloRunner2); thread3.start(); } }
三、以通知的方式停止线程
public class StopRunner implements Runnable { private int count = 0; private boolean runFlag = true; public void setRunFlag(boolean runFlag) { this.runFlag = runFlag; } public boolean isRunFlag() { return runFlag; } //在main方法中创建并启动1个线程。线程循环随机打印100以内的整数,直到主线程从键盘读取了“Q”命令。 @Override public void run() { while (runFlag) { System.out.println(Thread.currentThread().getName() + " : " + count++); if (count == 100) { count = 0; } } System.out.println("关键代码"); } } public class StopRunnerTest { public static void main(String[] args) { Runnable stopRunner = new StopRunner(); Thread thread = new Thread(stopRunner); thread.start(); int j = 0; for (int i = 0; i < 10000000; i++) { j++; } //thread.stop(); // 这个方法禁止使用, 因为它太暴力了!!!! // 用 多态的方式 ((StopRunner)stopRunner).setRunFlag(false); // 以通知的方式停止线程 } }
四、
//在main方法中创建并启动1个线程。线程循环随机打印100以内的整数,直到主线程从键盘读取了“Q”命令。 public class ExerStopRunner implements Runnable { private boolean runFlag = true; public void setRunFlag(boolean runFlag) { this.runFlag = runFlag; } @Override public void run() { while (runFlag) { System.out.println(Thread.currentThread().getName() + " : " + (int)(Math.random() * 100)); } System.out.println("关键代码"); } } public class ExerSleepRunnerTest { public static void main(String[] args) { Runnable runner = new ExerSleepRunner(); Thread thread = new Thread(runner); thread.start(); // 不断地观察loopFor的值 while (true) { if (((ExerSleepRunner)runner).isLoopOver()) { break; } try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } thread.interrupt(); } }
五、睡眠的几种解除方式
线程调用sleep方法后, 会使调用者线程进入睡眠状态, 不会抢夺CPU... 直到解除阻塞为止
线程进入sleep后, 几种解除方式 :
1) 时间到了, 自然醒
2) 被别的线程打断, 调用本线程对象的interrupt(), 被打断的线程会抛出异常
public class SleepRunner implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { int n = (int)(Math.random() * 200); System.out.println(n); try { Thread.sleep(200); } catch (InterruptedException e) { System.out.println("被打断了...."); } } System.out.println("for循环结束了"); try { Thread.sleep(30 * 1000); } catch (InterruptedException e) { System.out.println("在睡30秒时被打断"); } System.out.println("子线程OVER"); } } public class SleepRunnerTest { public static void main(String[] args) { Runnable runner = new SleepRunner(); Thread thread = new Thread(runner); thread.start(); System.out.println("main方法"); try { Thread.sleep(21 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); } }
六、
/**编写程序,在main方法中创建一个线程。线程每隔一定时间(200ms以内的随机时间)产生一个0-100之间的随机整数,
打印后将该整数放到集合中;
共产生100个整数,全部产生后,睡眠30秒,然后将集合内容打印输出;
在main线程中,唤醒上述睡眠的线程,使其尽快打印集合内容。*/
public class ExerSleepRunner implements Runnable { private boolean loopOver = false; public boolean isLoopOver() { return loopOver; } @Override public void run() { List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < 100; i++) { int n = (int)(Math.random() * 100); System.out.println(n); list.add(n); int randTime = (int)(Math.random() * 200); try { Thread.sleep(randTime); } catch (InterruptedException e) { System.out.println("在睡" + randTime + "毫秒时被打断"); } } loopOver = true; System.out.println("我要睡30秒了....."); try { Thread.sleep(30 * 1000); } catch (InterruptedException e) { System.out.println("在长睡时被打断, 严重不爽"); } System.out.println("我要打印集合了....."); Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer next = iterator.next(); System.out.println(next); } } } public class ExerSleepRunnerTest { public static void main(String[] args) { Runnable runner = new ExerSleepRunner(); Thread thread = new Thread(runner); thread.start(); // 不断地观察loopFor的值 while (true) { if (((ExerSleepRunner)runner).isLoopOver()) { break; } try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } thread.interrupt(); } }
七、
*******
通过构造器 对象关联
必须在线程启动前设置守护线程
守护线程特点 : 如果没有了用户线程, 所有守护线程自动终止.
在main方法中创建并启动1个线程。线程循环随机打印100以内的整数,直到主线程从键盘读取了“Q”命令。
import java.io.BufferedReader; import java.io.InputStreamReader; public class KeyListener implements Runnable { private Runnable runner; public KeyListener(Runnable runner) { super(); this.runner = runner; } @Override public void run() { BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new InputStreamReader(System.in)); String line = bufferedReader.readLine(); while (line != null) { // 处理数据 if (line.equalsIgnoreCase("q")) { // 停止子线程 ((ExerStopRunner)runner).setRunFlag(false); //break; // 自我终结 } // 继续读 line = bufferedReader.readLine(); } } catch (Exception e) { e.printStackTrace(); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (Exception e2) { } } } } } public class ExerStopRunnerTest { public static void main(String[] args) { //在main方法中创建并启动1个线程。线程循环随机打印100以内的整 Runnable runner = new ExerStopRunner(); Thread thread = new Thread(runner); thread.start(); //在main方法中创建并启动两个线程。第一个线程循环随机打印100以内的整数,直到第二个线程从键盘读取了“Q”命令。 Runnable runner2 = new KeyListener(runner); // 通过构造器 对象关联 Thread thread2 = new Thread(runner2); // 必须在线程启动前设置守护线程 thread2.setDaemon(true); // 守护线程特点 : 如果没有了用户线程, 所有守护线程自动终止. thread2.start(); } }
八 、线程的互斥锁
public class Counter implements Runnable { private int counter = 200; private Object object = new Object(); @Override //public synchronized void run() {// 粒度太大, 不好 public void run() { //synchronized (this) { // 粒度太大, 不好 for (int i = 0; i < 50; i++) { synchronized ("") { // 互斥锁对象, 下面的代码就会只在某一个线程中执行, 其他线程想执行下面的代码, 必须要等待. // 具有原子性的代码, 具有不可分割性 counter -= 2; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " : " + counter); } } //} } } public class CounterTest { public static void main(String[] args) { Runnable runner = new Counter(); Thread thread1 = new Thread(runner); thread1.setName("子线程1"); thread1.start(); Thread thread2 = new Thread(runner); thread2.setName("子线程2"); thread2.start(); } }
九、银行账户
银行有一个账户Account包含属性name, balance
写一个普通 类Deposit实现Runnable, 在run方法中存钱
有两个柜台(线程)分别同时向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。睡眠10毫秒
写一个普通 类Deposit实现Runnable, 在run方法中存钱
有两个柜台(线程)分别同时向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。睡眠10毫秒
问题:该程序是否有安全问题,如果有,如何解决?
扩展练习 :
一个柜台Deposit存3000元, 每次存1000,存3次
另一个柜台Withdraw取3000元, 每次取1000,取3次
【提示】
1,明确哪些代码是多线程运行代码,须写入run()方法
2,明确什么是共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。
一个柜台Deposit存3000元, 每次存1000,存3次
另一个柜台Withdraw取3000元, 每次取1000,取3次
【提示】
1,明确哪些代码是多线程运行代码,须写入run()方法
2,明确什么是共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。
public class RunnerDeposit implements Runnable{ private Account account; public RunnerDeposit(Account account) { this.account=account; } private double money; //Account account=new Account(); @Override public void run() { for (int i = 0; i < 3; i++) { synchronized ("") { money = account.getBalance(); account.setBalance(money+1000); //balance+=money; //余额 System.out.println(Thread.currentThread().getName()+"刚刚存了1000元"); //余额 System.out.println("账户余额:"+account.getBalance()+"元"); System.out.println(); try { Thread.sleep(10); } catch (InterruptedException e) { System.out.println("存钱中,请勿打扰...!"); } } } } } public class RunnerWithdraw implements Runnable{ private Account account; public RunnerWithdraw(Account account) { this.account=account; } private double money; @Override public void run() { for (int i = 0; i < 3; i++) { synchronized ("") { money = account.getBalance(); account.setBalance(money-1000); System.out.println(Thread.currentThread().getName()+"刚刚取了1000元"); //余额 System.out.println(); System.out.println("账户余额:"+account.getBalance()); try { Thread.sleep(10); } catch (InterruptedException e) { System.out.println("取钱中,请勿打扰...!"); } } } } } public class RunnerAccountMain { public static void main(String[] args) { Account account = new Account("张三", 0); Runnable runnerDeposit = new RunnerDeposit(account); Thread account1 = new Thread(runnerDeposit); account1.setName("柜台1:"); account1.start(); Thread account2 = new Thread(runnerDeposit); account2.setName("柜台2:"); account2.start(); Runnable runnerWithdraw = new RunnerWithdraw(account); Thread account3 = new Thread(runnerWithdraw); account3.setName("柜台3:"); account3.start(); } }