- 进程:是一个正在执行中的程序。
- 线程:就是进程中的一个独立的控制单元。
- 扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
- 定义类继承Thread。
- 复写Thread类中的run方法。
- 目的:将自定义代码存储在run方法。让线程运行。
需求:简单的卖票程序。
多个窗口同时买票。
创建线程的第二种方式:实现Runable接口
步骤:
- 定义类实现Runnable接口
- 覆盖Runnable接口中的run方法。
- 将线程要运行的代码存放在该run方法中。
- 通过Thread类建立线程对象。
将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
为什么要将Runnable接口的子类对象传递给Thread的构造函数。
因为,自定义的run方法所属的对象是Runnable接口的子类对象。
所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。
调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
现方式和继承方式有什么区别呢?
实现方式好处:避免了单继承的局限性。
在定义线程时,建立使用实现方式。
两种方式区别:
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable,线程代码存在接口的子类的run方法。
package nuddles.j2seDemo;
public class ThreadDemo {
* @param args
public static void main(String[] args) {
// TODO Auto-generated method stub
new SubThread().start();
new SubThread().start();
new Thread(new SubThread2()).start();
// 实现接口来创新线程
new Thread(new SubThread2()).start();
}
}
class SubThread extends Thread{
// 继承Thread来开启多线程
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
for (int i = 0; i <50; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
class SubThread2 implements Runnable{
// 实现接口创建多线程
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i <50; i++) {
System.out.println(Thread.currentThread().getName()+"........:"+i);
}
}
}
创建两个线程,和主线程交替运行。
原来线程都有自己默认的名称。
Thread-编号 该编号从0开始。
static Thread currentThread():获取当前线程对象。
getName(): 获取线程名称。
设置线程名称:setName或者构造函数。
class Thread1 extends Thread {
Thread1(String name){
super(name);
}
public void run(){
for (int i = 0; i < 60; i++) {
System.out.println(Thread.currentThread().getName());//Thread.currentThread()获取当前线程
}
}
}
public class ThreadTest{
public static void main(String[] args) {
Thread1 line1 =new Thread1("line1");
Thread1 line2 =new Thread1("line2");
line1.start();
line2.start();
}
}
7.多线程同时操作一个数据时的安全问题
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,
另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式。
就是同步代码块。
synchronized(对象)
{
需要被同步的代码
}
对象如同锁。持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
火车上的卫生间---经典。
同步的前提:
- 必须要有两个或者两个以上的线程。
- 必须是多个线程使用同一个锁。
- 必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源,
注:Synchronized(this)锁当前对象,两个不同线程持有同一个this执行会锁掉一个
class Ticket implements Runnable {
static int ticket = 2000;
Object obj = new Object();
public void run(){
while (true) {
synchronized(obj){
// obj作为锁的对象
if (ticket >0) {
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"sale ticket"+ticket--);
}
}
}
}
}
public class TicketTest{
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 =new Thread(t);
Thread t2 =new Thread(t);
Thread t3 =new Thread(t);
Thread t4 =new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
8.安全问题的另一种解决方案:同步代码块
格式:在 函数上加上 synchronized 修饰符即可。
同步函数和同步代码块的区别:
1、同步函数的锁是固定的 this。
2、同步代码块的锁是任意的对象。
建议使用同步代码块。
由于同步函数的锁是固定的 this, 同步代码块的锁是任意的对象, 那么如果同步函数和
同步代码块都使用 this 作为锁,就可以实现同步。
静态的同步函数使用的锁是 该函数所属字节码文件对象 ,可以用 getClass 方法获取,
也可以用当前类名.class 表示。
/*
需求:
银行有一个金库。
有三个储户分别存300员,每次存100,存3次。
目的:该程序是否有安全问题,如果有,如何解决?
如何找问题:
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。
*/
class Bank {
private int sum;
public synchronized void add(int n){
// 先在bank中定义存钱的方法
try{Thread.sleep(10);}catch(Exception e){}
// 睡一会
sum = sum+n;
System.out.println("sum="+sum);
}
}
class Cus implements Runnable{
private Bank bank = new Bank();
public void run(){
for (int i = 0; i<3; i++) {
bank.add(100);
// 调用三次,上锁
}
}
}
public class BankTest{
public static void main(String[] args) {
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t1.start();
t2.start();
t3.start();
}
}
package nuddles.j2seDemo;
public class ThreadDeadDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
new Thread(new SubThread(false)).start();
new Thread(new SubThread(true)).start();
// 先有自己的锁然后都想拿对方的锁,都抢不到,卡死了
}
}
class Lock{
public static Object locka = new Object();
public static Object lockb = new Object();
}
class SubThread implements Runnable{
boolean flag = false;
public SubThread(boolean flag) {
super();
this.flag = flag;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
if (flag) {
// 先拿a锁,再拿b锁
synchronized (Lock.locka) {
System.out.println(Thread.currentThread().getName()+".....locka");
synchronized (Lock.lockb) {
System.out.println(Thread.currentThread().getName()+".....lockb");
}
}
}else{
synchronized (Lock.lockb) {
// 先拿锁,再拿锁
System.out.println(Thread.currentThread().getName()+".....lockb");
synchronized (Lock.locka) {
System.out.println(Thread.currentThread().getName()+".....locka");
}
}
}
}
}
}
Thread-1.....locka
Thread-0.....lockb
等待/唤醒机制涉及的方法:
①wait():让线程处于冻结状态,被 wait 的线程会被存储到线程池中。
②notify():唤醒线程池中的一个线程(任何一个都有可能)。
③notifyAll():唤醒线程池中的所有线程。
P.S.
1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
2、必须要明确到底操作的是哪个锁上的线程!
3、 wait 和 sleep 区别?
①wait 可以指定时间也可以不指定。 sleep 必须指定时间。
②在同步中时,对 CPU 的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
为什么操作线程的方法 wait、 notify、 notifyAll 定义在了 object 类中,因为这些方法
是监视器的方法,监视器其实就是锁。
锁可以是任意的对象,任意的对象调用的方式一定在 object 类中。
/*编写程序,实现数据库的同步读写数据*/
package nuddles.j2seDemo;
public class DateBaseDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
DataBase database = new DataBase();
new Thread(new Input(database)).start();
new Thread(new Output(database)).start();
}
}
class DataBase{
String name;
String sex;
boolean flag;
public synchronized void set(String name,String sex){
// 将设值的方法定义在方法中
if (flag) {
try{this.wait();}catch(InterruptedException e ){}
// 如果真说明已经写入过数据了,那么应该等着
}else {
this.name = name;
this.sex = sex;
try{this.notify();}catch(Exception e ){}
// 没有就写入,然后叫醒out来输出
this.flag = true;
}
}
public synchronized void out(){
if(!flag){
try{this.wait();}catch(Exception e){}
// 如果没有数据,那么不读,等着
}
System.out.println(name+"....."+sex);
flag = false;
this.notify();
// 如果有数据读出来,读完后叫input赶紧来写
}
}
class Input implements Runnable{
private DataBase db;
Input(DataBase db){
this.db = db;
}
public void run(){
int i =0;
for (int j=0; j<20;j++ ) {
if (i==0) {
db.set("nuddlws","man");
}else{
db.set("张三","女");
}
i =(i+1)%2;
// 记住此方法用于轮转
}
}
}
class Output implements Runnable{
private DataBase db;
public Output(DataBase db) {
super();
this.db = db;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 20; i++) {
db.out();
}
}
}
package nuddles.j2seDemo;
public class ConsumerProducer {
public static void main(String[] args) {
Kaoya kaoya = new Kaoya();
new Thread(new Consumer(kaoya)).start();
new Thread(new Consumer(kaoya)).start();
new Thread(new Consumer(kaoya)).start();
new Thread(new Producer(kaoya)).start();
new Thread(new Producer(kaoya)).start();
new Thread(new Producer(kaoya)).start();
}
}
class Kaoya{
int number;
boolean flag;
@Override
public String toString() {
return "Kaoyan [number=" + number + "]";
}
public synchronized void produce() {
while (flag) {
// 用while第次都回去判断
try{this.wait();}catch(Exception e){}
}
number++;
System.out.println(Thread.currentThread().getName()+"生产都生产+"+this.toString());
this.flag = true;
this.notifyAll();
// 叫醒所有线程
}
public synchronized void consumer(){
while(!flag){
try{this.wait();}catch(Exception e){}
}
System.out.println(Thread.currentThread().getName()+"消费都吃了....."+this.toString());
this.flag = false;
this.notifyAll();
}
}
class Consumer implements Runnable{
private Kaoya kaoya;
public Consumer(Kaoya kaoya) {
super();
this.kaoya = kaoya;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
kaoya.consumer();
}
}
}
class Producer implements Runnable{
private Kaoya kaoya;
public Producer(Kaoya kaoya) {
super();
this.kaoya = kaoya;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
kaoya.produce();
}
}
}
12.jdk1.5新特性
同步代码块就是对于锁的操作是隐式的。
JDK1.5 以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将
隐式动作变成了显示动作。
Lock 接口: 出现替代了同步代码块或者同步函数, 将同步的隐式操作变成显示锁操作。
403
同时更为灵活,可以一个锁上加上多组监视器。
lock():获取锁。
unlock(): 释放锁, 为了防止异常出现, 导致锁无法被关闭, 所以锁的关闭动作要放在
finally 中。
Condition 接口: 出现替代了 Object 中的 wait、 notify、 notifyAll 方法。 将这些监视
器方法单独进行了封装,变成 Condition 监视器对象,可以任意锁进行组合。
Condition 接口中的 await 方法对应于 Object 中的 wait 方法。
Condition 接口中的 signal 方法对应于 Object 中的 notify 方法。
Condition 接口中的 signalAll 方法对应于 Object 中的 notifyAll 方法。
使用一个 Lock、一个 Condition 修改上面的多生产者-多消费者问题:
改写上个代码
package nuddles.j2seDemo;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
* @param args
public static void main(String[] args) {
// TODO Auto-generated method stub
Kaoji kaoya = new Kaoji();
new Thread(new Consumer1(kaoya)).start();
new Thread(new Consumer1(kaoya)).start();
new Thread(new Consumer1(kaoya)).start();
new Thread(new Producer1(kaoya)).start();
new Thread(new Producer1(kaoya)).start();
new Thread(new Producer1(kaoya)).start();
}
}
class Kaoji{
int number;
boolean flag;
@Override
public String toString() {
return "Kaoyan [number=" + number + "]";
}
ReentrantLock lock = new ReentrantLock();
Condition con = lock.newCondition();
// 定义锁和条件
public void produce() {
lock.lock();
try{
while (flag) {
// 用while第次都回去判断
try{con.await();}catch(Exception e){}
}
number++;
System.out.println(Thread.currentThread().getName()+"生产都生产+"+this.toString());
this.flag = true;
con.signalAll();
// 叫醒所有线程
}finally{
lock.unlock();
}
}
public void consumer(){
lock.lock();
try{
while(!flag){
try{con.await();}catch(Exception e){}
}
System.out.println(Thread.currentThread().getName()+"消费都吃了....."+this.toString());
this.flag = false;
con.signalAll();
}finally{
lock.unlock();
}
}
}
class Consumer1 implements Runnable{
private Kaoji kaoya;
public Consumer1(Kaoji kaoya) {
super();
this.kaoya = kaoya;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
kaoya.consumer();
}
}
}
class Producer1 implements Runnable{
private Kaoji kaoya;
public Producer1(Kaoji kaoya) {
super();
this.kaoya = kaoya;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
kaoya.produce();
}
}
}
13.如何停止线程?
只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于了冻结状态。
就不会读取到标记。那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();强制动作会发生 InterruptedException,一定要记得处理。
class Test implements Runnable {
boolean flag = true;
public synchronized void run(){
while (flag) {
try{this.wait();}catch(Exception e){}
setFlag(false);
System.out.println(Thread.currentThread().getName()+"over");
}
}
public void setFlag(boolean flag){
this.flag = flag;
}
}
public class StopThread{
public static void main(String[] args)throws Exception {
Test r = new Test();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
for (int i = 0; i<5000;i++) {
System.out.println(Thread.currentThread().getName()+"....running...");
if (i == 3000) {
r.setFlag(false);
t1.interrupt();//强制复苏线程
t2.interrupt();
}
}
System.out.println(Thread.currentThread().getName()+"..over..");
}
}
16.优先级:
SetPriority(1-10)设置优先级。
Thread.MAX_PRIORITY 10
Thread.MIN_PRIORITY 1
Thread.NORM_PRIORITY 5
17yield方法:
暂停当前正在执行的线程对象,并执行其他线程。