1、什么是程序、进程、线程
程序:为完成特定任务、用某种语言编写的一组指令的集合,即一段可以执行的静止的代码,保存在硬盘上的一个文件。
进程:正在运行的程序。同一个程序可以启动多个进程。在内存中处于激活状态,有生命周期。
线程:进程可进一步细化分为线程,是一个程序内部的一条执行路径。也称为进程中的子任务。
多线程又称作高并发。抢占式的,谁抢到谁执行。
2、何时需要多线程
程序需要同时执行两个及两个以上的任务。
程序需要实现一些需要等待的任务(用户输入、文件读写,网络操作、搜索)。
需要一些后台运行的程序。
多线程就是把不同方法压入到多个栈区中执行,其中main()为主线程的入口,run()为子线程的入口。
3、创建线程的两种方法
方法一(实现Runnable接口):
写一个具体类,实现Runnable接口。在类中实现Runnable接口中的run方法,这个run方法就是线程体。
创建这个类的对象,将对象作为实际参数传递给Thread类中的构造方法,创建Thread线程对象。
调用Thread线程对象中的start方法,开启线程,调用Runnable接口子类的run方法。
public class HelloThread implements Runnable{
private int i;
@Override
public void run() {
for(i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + "子线程:" + i);
}
}
}
public class HelloThreadTest {
public static void main(String[] args){
Runnable runner = new HelloThread();
Thread thread = new Thread(runner); //对象关联,创建一个新栈
thread.start(); //启动子线程,激活栈,并将run方法压入栈,两个栈同步执行
for(int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + "主线程:" + i);
}
}
}
并发的例子
public class StopRunner implements Runnable{
private int count = 0;
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
public boolean isFlag() {
return flag;
}
@Override
public void run(){
while(flag){
System.out.println(count++);
if(count == 200){
count = 0;
}
}
System.out.println("关键代码");
}
}
public class StopRunnerTest {
public static void main(String[] args){
Runnable runner = new StopRunner();
Thread thread = new Thread(runner);
thread.start();
for(int i = 0; i < 2000000000; i++){
}
((StopRunner)runner).setFlag(false); //以通知的方式停止线程
System.out.println("主线程结束");
}
}
练习:子线程1随机打印1-100随机整数,子线程二监控键盘,直到输入q为止停止程序,main方法调用这两个子线程。
public class RandomRunnerable implements Runnable{
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run(){
while (flag) {
System.out.println((int) (Math.random() * 100));
}
}
}
public class KeyListener implements Runnable{
//对象关联
private Runnable runner;
public KeyListener(Runnable runner){
this.runner = runner;
}
@Override
public void run(){
InputStream in = System.in;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
try {
inputStreamReader = new InputStreamReader(in);
bufferedReader = new BufferedReader(inputStreamReader);
String line = bufferedReader.readLine();
while(line != null){
if(line.equalsIgnoreCase("q")){
((RandomRunnerable) runner).setFlag(false);
break;
}
line = bufferedReader.readLine();
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(bufferedReader != null){
try {
bufferedReader.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
public class RandomRunnerableTest {
public static void main(String[] args){
Runnable runner= new RandomRunnerable();
Thread thread = new Thread(runner);
thread.start();
Runnable runner1 = new KeyListener(runner);
Thread thread1 = new Thread(runner1);
thread1.start();
}
}
方法二(继承Thread类)
写一个类,继承Thread,并重写run方法。此方法就是线程体。
创建这个类的对象,相当于创建了线程对象。
调用这个线程对象的start方法。
class MyThread extends Thread{
@Override
public void run(){
for(int i = 0; i < 100; i++){
System.out.println(currentThread().getName() + i);
}
}
}
public class ThreadTest {
public static void main(String[] args){
MyThread myThread = new MyThread();
myThread.start();
System.out.println("main..");
for(int i = 0; i < 100; i++){
System.out.println(myThread.currentThread().getName() + i);
}
}
}
两种方法的区别与联系
实现Runnable:线程代码存放在接口的子类的run方法中。
继承Thread:线程代码存放Thread子类run方法中。
方法一的好处:
避免了单继承的局限性。
多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程处理同一份资源。
4、几个方法
void join() 谁调用谁执行,其他线程先休息一下。
static void sleep(long millis) 让当前线程(正在执行此方法的栈的线程)进入睡眠转态一段时间,注意与调用者无关,而是此方法在哪个栈中执行,在哪个栈中执行,哪个栈休眠。
解除sleep状态两种途径:休眠时间到了或者被别的线程打断(在别的线程中调用此线程对象的interrupt方法),当被别的线程打断会抛出异常。
5、线程同步安全问题
多个线程执行的不确定性引起执行结果的不确定性(同时对同一个数据的修改)
public class Counter implements Runnable{
private int counter = 200;
@Override
public void run(){
for(int i = 0; i < 50; i++){
counter -= 2;
try {
Thread.sleep(10);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + counter);
}
}
}
public class CounterTest {
public static void main(String[] args){
Counter counter = new Counter();
Thread thread = new Thread(counter);
thread.start();
Thread thread1 = new Thread(counter);
thread1.start();
}
}
两个线程同时访问同一数据造成结果的错误,解决办法,上锁!
public class Counter implements Runnable{
private int counter = 200;
@Override
public void run(){
for(int i = 0; i < 50; i++){
synchronized (this){ //()中是一个锁对象,任意对象都可以,称为互斥锁,只允许一个线程进入执行,其他线程等待
//以下代码具有了原子性,不可分割
counter -= 2;
try {
Thread.sleep(10);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + counter);
}
}
}
}
练习:
编写程序,在main方法中创建两个线程。线程1每隔一定时间(200ms以内的随机时间)产生一个0-100之间的随机整数,打印后将该整数放到集合中;
共产生100个整数,全部产生后,睡眠30秒;
在线程2中,唤醒上述睡眠的线程1,并获取线程1中的集合并打印集合内容。
public class ExerRunner implements Runnable {
// 提醒子线程, 此主存中的属性不要制作副本...
private volatile boolean over = false;
private List<Integer> list = new ArrayList<Integer>();
public boolean isOver() {
return over;
}
public List<Integer> getList() {
return list;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
int rand = (int)(Math.random() * 100);
System.out.println(rand);
list.add(rand);
int time = (int)(Math.random() * 200);
try {
Thread.sleep(time);
} catch (InterruptedException e) {
System.out.println("在短睡时被打断, 无所谓");
}
}
over = true;
System.out.println("要睡30秒了");
try {
Thread.sleep(30 * 1000);
} catch (InterruptedException e) {
System.out.println("在长睡时被打断.... 很生气");
}
System.out.println("睡起来后 ");
}
}
public class Observer implements Runnable {
private Runnable runner;
private Thread thread;
public Observer(Runnable runner, Thread thread) {
this.runner = runner;
this.thread = thread;
}
@Override
public void run() {
// 观察者不断的观察
while (!((ExerRunner)runner).isOver());
thread.interrupt();
// 获取集合
List<Integer> list = ((ExerRunner)runner).getList();
for (Integer integer : list) {
System.out.println("观察者 : " + integer);
}
}
}
public class ExerRunnerTest {
public static void main(String[] args) {
Runnable runner = new ExerRunner();
Thread thread = new Thread(runner);
thread.start();
Runnable runner2 = new Observer(runner, thread); // 对象关联
Thread thread2 = new Thread(runner2);
thread2.start();
}
}
锁对象的wait方法(消费者):使得当前线程挂起并放弃CPU,放弃锁对象,这样别的线程就可以访问并修改共享资源。
锁对象的nofity方法(生产者):唤醒正在排队等待同步资源线程中优先级最高者。
练习:实现线程一、线程二交替打印1-100
class Counter implements Runnable {
private int n = 1;
@Override
public void run() {
for (int i = 0; i < 50; i++) {
synchronized ("") {
System.out.println(Thread.currentThread().getName() + " : " + n++);
"".notify();
if (i < 49) {
try {
"".wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class WaitNotifyTest {
public static void main(String[] args) {
Runnable runner = new Counter();
Thread thread1 = new Thread(runner);
Thread thread2 = new Thread(runner);
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}
练习:
银行有一个账户Account包含属性name, balance,写一个普通类Deposit实现Runnable, 在run方法中存钱,有两个柜台(线程)分别同时向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。睡眠10毫秒。
另一个柜台Withdraw取3000元, 每次取1000,取3次。
public class Account {
private String name;
private double balance;
public Account() {
}
public Account(String name, double balance) {
super();
this.name = name;
this.balance = balance;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account [name=" + name + ", balance=" + balance + "]";
}
}
public class Deposit implements Runnable {
private Account account;
public Deposit(Account account) {
super();
this.account = account;
}
@Override
public void run() {
//向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。睡眠10毫秒
for (int i = 0; i < 6; i++) {
synchronized ("") {
int money = 500;
account.setBalance(account.getBalance() + money);
System.out.println(Thread.currentThread().getName() + " 存完钱后 : " + account);
"".notify();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Withdraw implements Runnable {
private Account account;
public Withdraw(Account account) {
super();
this.account = account;
}
@Override
public void run() {
// 取钱
for (int i = 0; i < 3; i++) {
synchronized ("") {
if (account.getBalance() < 1000) {
System.out.println("钱不够, 进入等待");
try {
"".wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int money = 1000;
account.setBalance(account.getBalance() - money);
System.out.println(Thread.currentThread().getName() + " 取钱后 : " + account);
}
}
}
}
public class AccountTest {
public static void main(String[] args) {
Account account = new Account("张三", 0);
Runnable runner1 = new Deposit(account);
Runnable runner2 = new Withdraw(account);
Thread thread3 = new Thread(runner2);
thread3.setName("取钱柜台1");
thread3.start();
Thread thread1 = new Thread(runner1);
thread1.setName("存钱柜台1");
//Thread thread2 = new Thread(runner1);
//thread2.setName("存钱柜台2");
thread1.start();
//thread2.start();
}
}
当前锁对象的wait方法可实现释放锁。
死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源。
死锁的解决办法:不要嵌套锁(synchronized),即使必须要嵌套,保证锁对象尽可能的少。
可重入锁:同一个线程可以无限次获取同一个锁。
本章重点:重原理轻应用