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 Listlist = new ArrayList (); public boolean isOver() { return over; } public List 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 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),即使必须要嵌套,保证锁对象尽可能的少。
可重入锁:同一个线程可以无限次获取同一个锁。
本章重点:重原理轻应用
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)