javaSE 线程并发

接上一篇https://blog.csdn.net/weixin_44035017/article/details/101293121

线程同步

在多线程的程序中,容易发生资源抢占的问题,如同两个人同时过独木桥。所以在多线程编程中需要防止这些资源的冲突。java提供线程同步机制来防止资源访问的冲突。

(1)线程安全

比如火车票售票系统,当票数大于0时,就会售出。但当只剩一张票的时候,两个线程同时访问这段代码,第一个线程将票售出,与此同时第二线程也已经执行完成判读票是否大于0的操作,并得出票大于0的结论,于是也将票售出,两个线程都将票售出,但实际票只有一张。
1.火车票

public class SafeTest implements Runnable {
    int num=10;
    @Override
    public void run() {
        while(true) {
            if (num > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("票数:" + num--);
            }
        }
    }

    public static void main(String[] args) {
        SafeTest t=new SafeTest();
        Thread thread1=new Thread(t);
        Thread thread2=new Thread(t);
        Thread thread3=new Thread(t);
        Thread thread4=new Thread(t);
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}
输出结果:
票数:10
票数:9
票数:9
票数:9
票数:8
票数:5
票数:6
票数:7
票数:4
票数:3
票数:2
票数:4
票数:1
票数:-2
票数:-1
票数:0

上述代码结果可以看出,产生了脏数据,这就是线程不同步导致的。比如线程A刚进入if 语句的时候,num=1;但在这时线程B刚执行完num–,这时num就变成了0,再假如其他线程也刚执行完num–,这样num就变成了负数。这就是线程不同步导致的
除了负数还存在相同票数,这就是线程的工作空间与主存的交互问题
在这里插入图片描述
假设主存有十张票,线程a要将主存的数据拷贝到工作空间,所以线程a为十张票,线程a做–操作,然后就9张票返回去,覆盖主存的十张票,但在覆盖之前线程b已经拷贝了之前的十张票了,线程b做–操作后返回主存还是9张票,这就是线程不同步导致的不安全问题
2.取钱


public class Test {
	public static void main(String[] args) {
		//账户
		Account account =new Account(100,"张三");
		Take you = new Take(account,80,"张三取钱");
		Take wife = new Take(account,90,"张三的女朋友取钱");
		you.start();
		wife.start();
	}
}
class Account{
	int money; //金额
	String name; //名称
	public Account(int money, String name) {
		this.money = money;
		this.name = name;
	}
}
//模拟取款
class Take extends Thread{
	Account account ; //取钱的账户
	int TakeMoney ;//取的钱数
	int packetMoney ; //口袋的总数

	public Take(Account account, int drawingMoney,String name) {
		super(name);
		this.account = account;
		this.TakeMoney = drawingMoney;
	}
	@Override
	public void run() {
		if(account.money -TakeMoney<0) {  
			return;
		}
		try {
			Thread.sleep(1000);   //模拟延时
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		account.money -=TakeMoney;
		packetMoney +=TakeMoney;
		System.out.println(this.getName()+"   账户余额为:"+account.money);
		System.out.println(this.getName()+"   口袋的钱为:"+packetMoney);
	}
} 
输出结果:
张三的女朋友取钱   账户余额为:-70
张三的女朋友取钱   口袋的钱为:90
张三取钱   账户余额为:-70
张三取钱   口袋的钱为:80

显然加上了if(account.money -TakeMoney<0) 并没有用,这也是线程不安全造成的

3.操作容器

public class Test {
	public static void main(String[] args) throws InterruptedException {
		List<String> list = new ArrayList<String>();
		for(int i=0;i<10000;i++) {
			new Thread(()->{
				list.add(Thread.currentThread().getName());
			}) .start();
		}
		System.out.println(list.size());
	}
}
输出结果:
9986   

显然发生了数据丢失,没有达到10000。这也是线程不安全造成的

当需要做数据的修改操作时,应该考虑线程的安全问题。仅仅只做读取数据时,可以不用考虑线程同步

(2)线程并发

线程并发是同一个对象多个线程同时操作
比如账户
同一个对象:大家各自操作各自的账户,就不会出现问题
多个线程::同一个对象,只有一个人去操作这个对象,也不会出现问题
同时:多个线程都在同时操作时,才会出现问题。比如同一个账户,a和b取钱,但a上午取得,b下午取得,也不会发生问题

解决线程并发的方法:队列+锁
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比 如:上公交车,大家都想上去。解决办法就是排队。
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。 这时候,我们就需要用到“线程同步”。 线程同步其实就 是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
然后在队列里选一个线程,那如何知道某个线程已经获得对象了呢,这就需要锁机制了。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带 来了访问冲突的问题。为了保证数据在方法中被访问时的正确性,在访问 时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源, 其他线程必须等待,使用后释放锁即可。

线程同步存在一些问题:
一个线程持有锁会导致其它所有需要此锁的线程挂起;
在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时, 引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁可能会导致优先级倒置,引起性能问题

(3)线程同步

synchronized关键字,它包括两种用法:synchronized 方法和 synchronized 块。

锁一个对象(资源),可以锁一个具体的对象。
如果在本类中,你操作的是一个成员方法,那么你锁的就是this.如果是一个静态方法,那么它锁的就是对象的模子(class对象 反射机制)

synchronized 方法控制对“成员变量|类变量”对象的访问,若将一个大的方法声明为synchronized 将会大大影响效率。比如一个方法里面操作对象(资源)的两个属性:a属性和b属性 。但仅仅b属性涉及修改,a属性只读。当你加锁了,其他像访问 a 的也只能等待,但是 修改才会引起线程的不安全。这就是synchronized的范围太大了。这可以借助synchronized块
范围太大影响效率,但范围太小可能锁不住。这也是一个难点

加锁时,还应该考虑锁的对不对,比如两个对象a,b,需要操作b对象,但是锁成了a对象或者this对象,还是会造成不安全

(2)线程同步机制

解决资源共享问题,就是给定时间内只允许一个线程访问共享资源,这就需要给共享资源上锁。上了锁,其他线程无法访问这个资源。

1.同步方法

synchronized void f()      //在方法前加synchronized 关键字
public class SafeTest implements Runnable {
    int num=10;
    private boolean flag = true;
    @Override
    public void run() {
        while(flag) {
         	try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
               sell();     
        }
    }
    private synchronized void sell(){    //线程同步方法
        if (num > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("票数:" + num--);
        }else{
        	flag = false;
			return ;
		}
    }

    public static void main(String[] args) {
        SafeTest t=new SafeTest();
        Thread thread1=new Thread(t);
        Thread thread2=new Thread(t);
        Thread thread3=new Thread(t);
        Thread thread4=new Thread(t);
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}
输出结果:
票数:10
票数:9
票数:8
票数:7
票数:6
票数:5
票数:4
票数:3
票数:2
票数:1

synchronized方法锁的是对象(资源),上面的sell()方法是成员方法,锁的是this对象,而sell()方法里就有操作this对象的动作 num–.如果操作的不是this对象,那就没锁住
比如下面

public class UnsafeTest02 {
    public static void main(String[] args) {
        //账户
        Account account =new Account(100,"张三");
        Take you = new Take(account,80,"张三取钱");
        Take wife = new Take(account,90,"张三的女朋友取钱");
        you.start();
        wife.start();
    }
}
class Account{
    int money; //金额
    String name; //名称
    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//模拟取款
class Take extends Thread{
    Account account ; //取钱的账户
    int TakeMoney ;//取的钱数
    int packetMoney ; //口袋的总数

    public Take(Account account, int drawingMoney,String name) {
        super(name);
        this.account = account;
        this.TakeMoney = drawingMoney;
    }

    @Override
    public void run() {
       take();
    }
    private synchronized void take(){    //锁的是this
        if(account.money -TakeMoney<0) {
            return;
        }
        try {
            Thread.sleep(1000);   //模拟延时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money -=TakeMoney;
        packetMoney +=TakeMoney;
        System.out.println(this.getName()+"   账户余额为:"+account.money);
        System.out.println(this.getName()+"   口袋的钱为:"+packetMoney);
    }
}
输出结果:
张三取钱   账户余额为:-70
张三的女朋友取钱   账户余额为:-70
张三取钱   口袋的钱为:80
张三的女朋友取钱   口袋的钱为:90

synchronized void take()锁的是this,但是需要加锁的不是this而是account对象。所以还是造成了线程的不安全.所以应该使用synchronized同步块

2.同步块
使用synchronized关键字

synchronized(Object){

}

Object称之为同步监视器 Object可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监 视器是this即该对象本身,或class即类的模子

通常将共享资源放置在synchronized定义的区域内,这样当其他线程也获取到这个锁时,必须等待锁被释放时才能被才能进入该区域。Object为任意一个对象,每个对象都存在一个标志位,并具有两个值,0和1.一个线程运行到同步块时,首先检查该对象的标志位,如果为0,说明此同步块中存在其他线程在运行。这时,该线程处于就绪状态,直到处于同步块中的线程执行完同步块的代码为止。这时该对象的标志位设置为1,该线程才能执行同步块中的代码,并将Object对象的标志位设为0,防止其他线程执行同步块中的代码。

public class UnsafeTest02 {
    public static void main(String[] args) {
        //账户
        Account account =new Account(100,"张三");
        Take you = new Take(account,80,"张三取钱");
        Take wife = new Take(account,90,"张三的女朋友取钱");
        you.start();
        wife.start();
    }
}
class Account{
    int money; //金额
    String name; //名称
    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//模拟取款
class Take extends Thread{
    Account account ; //取钱的账户
    int TakeMoney ;//取的钱数
    int packetMoney ; //口袋的总数

    public Take(Account account, int drawingMoney,String name) {
        super(name);
        this.account = account;
        this.TakeMoney = drawingMoney;
    }

    @Override
    public void run() {
       take();
    }
    private  void take(){
        synchronized (account) {
            if (account.money - TakeMoney < 0) {
           		System.out.println("余额不足");
                return;
            }
            try {
                Thread.sleep(1000);   //模拟延时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money -= TakeMoney;
            packetMoney += TakeMoney;
            System.out.println(this.getName() + "   账户余额为:" + account.money);
            System.out.println(this.getName() + "   口袋的钱为:" + packetMoney);
        }
    }
}
输出结果:
张三取钱   账户余额为:20
张三取钱   口袋的钱为:80
余额不足

这就不会出现负数,当张三取钱完还剩20,张三的女朋友取钱90时,余额不足取不了钱
当多个线程都进来的时候都需要等,但是账户此时已经没钱了,后面的线程排队进入资源肯定是没有结果的。就好比10个人去食堂窗口买饭,到第五个人的时候饭没了,但是synchronized关键字导致所有人都必须排队,明明没饭了还要去排队去访问资源。所以可以在synchronized前加一个判断来提升并发的性能。

private  void take(){
        if(account.money<=0){   //性能优化  
            return;
        }
        synchronized (account) {
            if (account.money - TakeMoney < 0) {
                System.out.println("余额不足");
                return;
            }
            try {
                Thread.sleep(1000);   //模拟延时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money -= TakeMoney;
            packetMoney += TakeMoney;
            System.out.println(this.getName() + "   账户余额为:" + account.money);
            System.out.println(this.getName() + "   口袋的钱为:" + packetMoney);
        }
    }
操作容器

public class Test {
	public static void main(String[] args) throws InterruptedException {
		List<String> list = new ArrayList<String>();
		for(int i=0;i<10000;i++) {
			new Thread(()->{
				//同步块
				synchronized(list) {
					list.add(Thread.currentThread().getName());
				}
			}) .start();
		}
		Thread.sleep(10000);
		System.out.println(list.size());
	}
}
输出结果:
10000

加入Thread.sleep(10000)是为了等所有线程执行完再去统计。没有synchronized同步块,等待之后统计还是没有用

package com.THREAD;

public class SafeTest implements Runnable {
    int num=10;
    @Override
    public void run() {
        while(true) {
            synchronized (this) {   //加锁
                if (num > 0) {
                    try {
                        Thread.sleep(0);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("票数:" + num--);
                }
            }
        }
    }

    public static void main(String[] args) {
        SafeTest t=new SafeTest();
        Thread thread1=new Thread(t);
        Thread thread2=new Thread(t);
        Thread thread3=new Thread(t);
        Thread thread4=new Thread(t);
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}
输出结果:
票数:10
票数:9
票数:8
票数:7
票数:6
票数:5
票数:4
票数:3
票数:2
票数:1

从上面结果可以看出,输出结果是按顺序依次递减的,这就是线程同步的效果

多线程并发性能分析

同步块可以范围更小的锁定资源,在范围更小的情况下尽可能的提升它的性能

(1)范围太大,导致效率太低
public class SafeTest implements Runnable {
    int num=10;
    private boolean flag = true;
    @Override
    public void run() {
        while(flag) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }
    }
    private  void sell(){    //线程同步方法
        synchronized(this) {
            if (num > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("票数:" + num--);
            } else {
                flag = false;
                return;
            }
        }
    }

    public static void main(String[] args) {
        SafeTest t=new SafeTest();
        Thread thread1=new Thread(t);
        Thread thread2=new Thread(t);
        Thread thread3=new Thread(t);
        Thread thread4=new Thread(t);
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}输出结果:
票数:10
票数:9
票数:8
票数:7
票数:6
票数:5
票数:4
票数:3
票数:2
票数:1

很显然锁住了,但是范围太大 ,效率低下.思考一下只是num做改变,所以考虑能不能只锁num

(2)对象不对,导致线程不安全
public class SafeTest implements Runnable {
    int num=10;
    private boolean flag = true;
    @Override
    public void run() {
        while(flag) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }
    }
    private  void sell(){   
        synchronized((Integer)num) {    //
            if (num > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("票数:" + num--);
            } else {
                flag = false;
                return;
            }
        }
    }

    public static void main(String[] args) {
        SafeTest t=new SafeTest();
        Thread thread1=new Thread(t);
        Thread thread2=new Thread(t);
        Thread thread3=new Thread(t);
        Thread thread4=new Thread(t);
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}
输出结果:
票数:10
票数:9
票数:8
票数:8
票数:7
票数:6
票数:5
票数:4
票数:3
票数:2
票数:1
票数:0
票数:-1

很显然没锁住,这是因为synchronized(Obj)只能传入对象,而(Integer)num每次是不同的对象,比如num为1时的Integer对象和num为2时的Integer对象是不同的。而且flag也没有锁住

(3)范围太小导致没锁住
如果只锁flag

  private  void sell(){    
       	synchronized(this) {
				if(num<=0) {
					flag = false;
					return ;
				}
			}
			             //模拟延时
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(num--);
    }

这是锁不住的,假设三个线程a,b,c,还剩一张票,当a线程进入synchronized时锁住了发现还有票没有改变flag,然后退出synchronized块,并等待200毫秒。在等待的过程中并没有做num–操作。因为等待时已经释放了锁,所以线程b进入了synchronized块,发现还有一张票。然后退出synchronized块线程b等待。
这时线程c可以进入synchronized块。进入了之后,因为线程a,b都在等待,它们都没有做num–操作,所以这时线程c还是没有改变flag,它也继续往下走了。线程a,b,c都进入这个方法,所以当线程a做–,还剩0张票,但此时线程b和c已经进来了,所以他们也可以做 - -操作,所以还会导致负数造成线程不安全

 private  void sell(){    
        if(num<=0) {//考虑的是没有票的情况
            flag = false;
            return ;
        }
        synchronized(this) {
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("票数"+num--);
        }
    }

输出结果:
票数10
票数9
票数8
票数7
票数6
票数5
票数4
票数3
票数2
票数1
票数0
票数-1
票数-2

如果这么锁,还是会导致线程不安全。情况还是跟刚才一样,在休眠未改变num时,线程a,b,c都已经判断完if(num<=0),然后去排队。

(4)线程安全并尽可能锁定合理范围
  private  void sell(){    
        if(num<=0) {//考虑的是没有票的情况
            flag = false;
            return ;
        }
        synchronized(this) {
            if (num <= 0) {     //考虑最后的1张票
                flag = false;
                return;
            }
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("票数"+num--);
        }
    }
输出结果:
票数10
票数9
票数8
票数7
票数6
票数5
票数4
票数3
票数2
票数1

合理的范围不是指代码 ,是指数据的完整性
这是个双重检测,主要考虑零界值得问题

所以多用synchronized块,少用synchronized方法

死锁

多个线程各自占有一些共享资源,并且互相 等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步块同时拥有“两个以上对 象的锁”时(锁套锁),就可能会发生“死锁”的问题

比如一手交钱,一手交货
两个人a,b,a有货,b有钱。a说一手交钱,一手交货。b说一手交货,一手交钱。两个人都不松口,不信任对方。这就造成死锁局面

public class DeadLock {
    public static void main(String[] args) {
        Change c1=new Change(1,"张三");
        Change c2=new Change(0,"李四");
        c1.start();
        c2.start();
    }
}

class Thing{

}
class Money{

}

class Change extends Thread{
    static Thing thing=new Thing();
    static Money money=new Money();
    int choice;
    String person;
    public Change(int choice,String person){
        this.choice=choice;
        this.person=person;
    }

    @Override
    public void run() {
        change();
    }

    private void change() {
        if(choice==0){
            synchronized (thing){
                System.out.println("我有货,你交钱");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (money){
                    System.out.println(this.person+"拿到钱了");
                }
            }
        }else{
            synchronized (money){
                System.out.println("我有钱,你交货");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (thing){
                    System.out.println(this.person+"拿到货了");
                }
            }
        }
    }
}

在这里插入图片描述
这就造成了死锁的尴尬局面,对方互相拥有对方资源的锁,互相等待对方释放资源的锁,解决办法就是不要锁套锁。

public class DeadLock {
    public static void main(String[] args) {
        Change c1=new Change(1,"张三");
        Change c2=new Change(0,"李四");
        c1.start();
        c2.start();
    }
}

class Thing{

}
class Money{

}

class Change extends Thread{
    static Thing thing=new Thing();
    static Money money=new Money();
    int choice;
    String person;
    public Change(int choice,String person){
        this.choice=choice;
        this.person=person;
    }

    @Override
    public void run() {
        change();
    }

    private void change() {
        if(choice==0){
            synchronized (thing){
                System.out.println("我有货,你交钱");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (money){
                System.out.println(this.person+"拿到钱了");
            }
        }else{
            synchronized (money){
                System.out.println("我有钱,你交货");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            synchronized (thing){
                System.out.println(this.person+"拿到货了");
            }
        }
    }
}

输出结果:
我有货,你交钱
我有钱,你交货
张三拿到货了
李四拿到钱了

并发容器

public class Test {
	public static void main(String[] args) throws InterruptedException {
		List<String> list = new ArrayList<String>();
		for(int i=0;i<10000;i++) {
			new Thread(()->{
				//同步块
				synchronized(list) {
					list.add(Thread.currentThread().getName());
				}
			}) .start();
		}
		Thread.sleep(10000);
		System.out.println(list.size());
	}
}
输出结果:
10000

在操作容器时,我们使用synchronized块将list锁住,实际在Juc的并发编程中,有对应的并发容器,其内部已经实现了锁
在java.util.concurrent包下有CopyOnWriteArrayList

public class Test {
	public static void main(String[] args) throws InterruptedException {
		CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
		for(int i=0;i<10000;i++) {
			new Thread(()->{
				list.add(Thread.currentThread().getName());
			}) .start();
		}
		Thread.sleep(10000);
		System.out.println(list.size());
	}
}
输出结果:
10000

线程协作

生产者消费者模式

生产者和消费者问题
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费;
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生 产并等待,直到仓库中的产品被消费者取走为止;
如果仓库中放有产品,则消费者可以将产品取走消费,否则停 止消费并等待,直到仓库中再次放入产品为止。

线程通信

这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消 费者之间相互依赖,互为条件
对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后, 又需要马上通知消费者消费 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品。
在生产者消费者问题中,仅有synchronized是不够的 。synchronized可阻止并发更新同一个共享资源,实现了同步 。synchronized不能用来实现不同线程之间的消息传递(通信)
有两种解决办法
1.管程法
建立一个缓冲区。
生产者:负责生产数据的模块(这里模块可能是:方法、对象、线程、进程);
消费者:负责处理数据的模块(这里模块可能是:方法、对象、线程、进程);
缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”;。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。
这样就解决了闲时太闲,忙时太忙的问题。

2.信号灯法
类似红绿灯

如何唤醒和等待,java的Object类就提供了方法

方法名作用
final void wait()表示线程一直等待,直到其他线程通知, 与sleep不同,会释放锁
final void wait(long timeout)指定等待的毫秒数
final void notifiy() 唤醒一个处于等待状态的线程
final void notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

都只能在同步方法或者同步代码块中使用,否则会抛出异常

(1)管程法

模拟KFC卖汉堡

package Learn;

public class ThreadCommunication {
    public static void main(String[] args) {
        Buffer buffer=new Buffer();
        new Productor(buffer).start();
        new Customer(buffer).start();
    }
}

class Customer extends Thread{
    Buffer buffer;
    public Customer(Buffer buffer){
        this.buffer=buffer;
    }
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println("消费第"+buffer.pop().id+"个汉堡");
        }
    }
}
class Productor extends Thread{
    Buffer buffer;
    public Productor(Buffer buffer){
        this.buffer=buffer;
    }
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println("生产第"+i+"个汉堡");
            buffer.push(new Harmburger(i));
        }
    }
}

class Harmburger{
    int id;
    public Harmburger(int id) {
        this.id = id;
    }
}

class Buffer{
    Harmburger harmburgers[]=new Harmburger[10];
    int count=0;
    public synchronized void push(Harmburger burger){
        harmburgers[count++]=burger;
    }

    public synchronized Harmburger pop(){
        count--;
        Harmburger harmburger=harmburgers[count];
        return  harmburger;
    }
}

这是没有加唤醒和等待的情况
在这里插入图片描述
报异常,数组越界。生产者一直在那生产,没有等待。尽管有消费者拿汉堡,但是还是超过了缓冲区的数组大小。

package Learn;

public class ThreadCommunication {
    public static void main(String[] args) {
        Buffer buffer=new Buffer();
        new Productor(buffer).start();
        new Customer(buffer).start();
    }
}

class Customer extends Thread{
    Buffer buffer;
    public Customer(Buffer buffer){
        this.buffer=buffer;
    }
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println("消费第"+buffer.pop().id+"个汉堡");
        }
    }
}
class Productor extends Thread{
    Buffer buffer;
    public Productor(Buffer buffer){
        this.buffer=buffer;
    }
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println("生产第"+i+"个汉堡");
            buffer.push(new Harmburger(i));
        }
    }
}

class Harmburger{
    int id;
    public Harmburger(int id) {
        this.id = id;
    }
}

class Buffer{
    Harmburger harmburgers[]=new Harmburger[10];
    int count=0;
    public synchronized void push(Harmburger burger){
        if(count==harmburgers.length){
            try {
                this.wait();   //线程阻塞  当消费者通知生产者时,解除阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        harmburgers[count++]=burger;
        this.notifyAll();   //存在汉堡,通知消费者
    }
    public synchronized Harmburger pop(){
        if(count==0){
            try {
                this.wait();     //线程阻塞    当生产者通知消费者时,阻塞解除
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        Harmburger harmburger=harmburgers[count];
        this.notifyAll();    //存在空间 通知生产者可以生产
        return  harmburger;
    }
}

在这里插入图片描述
没有发生阻塞

(2)信号灯法

使用标志位,模拟信号灯

package Learn;

public class ThreadCommunication2 {
    public static void main(String[] args) {
            Phone phone=new Phone();
            new Father(phone).start();
            new Son(phone).start();
    }
}

class Father extends  Thread{
    Phone phone;
    public Father(Phone phone) {
        this.phone = phone;
    }
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            phone.Fathertalk("老爸的第"+i+"句话");
        }
    }
}
class Son extends Thread{
    Phone phone;
    public Son(Phone phone) {
        this.phone = phone;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            phone.SonReceive();
        }
    }
}

class Phone{
    String talk;
    boolean flag=true;

    public synchronized void Fathertalk(String talk){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("老爸说:"+talk);
        this.notifyAll();
        this.flag=!this.flag;
    }
    public synchronized void SonReceive(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("儿子收到");
        this.notifyAll();
        this.flag=!this.flag;
    }
}
输出结果:
老爸说:老爸的第0句话
儿子收到
老爸说:老爸的第1句话
儿子收到
老爸说:老爸的第2句话
儿子收到
老爸说:老爸的第3句话
儿子收到
老爸说:老爸的第4句话
儿子收到
老爸说:老爸的第5句话
儿子收到
老爸说:老爸的第6句话
儿子收到
老爸说:老爸的第7句话
儿子收到
老爸说:老爸的第8句话
儿子收到
老爸说:老爸的第9句话
儿子收到

任务定时调度

(1)Timer/TimerTask

1.Timer
类似闹钟,本身就是线程。对应于每个Timer对象是单个后台线程,用于依次执行所有定时器的所有任务
线程调度任务以供将来在后台线程中执行的功能。 任务可以安排一次执行,或定期重复执行。
在最后一次对Timer对象的引用后,所有未完成的任务已完成执行,定时器的任务执行线程正常终止(并被收集到垃圾回收。 如果主叫方想要快速终止定时器的任务执行线程,则调用者应该调用定时器的cancel方法。
这个类是线程安全的:多个线程可以共享一个单独的Timer对象,而不需要外部同步。

构造方法

Timer() ;     //创建一个新的计时器
Timer(boolean isDaemon);    //创建一个新的定时器,可以设置为守护线程
Timer(String name) ;   //其相关线程具有指定的名称。
Timer(String name, boolean isDaemon) ;

常用方法

方法说明
schedule(TimerTask task, Date time)在指定的时间安排指定的任务执行。
schedule(TimerTask task, Date firstTime, long period)从指定 的时间开始 ,对指定的任务执行重复的 固定延迟(每隔一段时间)执行
schedule(TimerTask task, long delay)在指定的延迟之后安排指定的任务执行
schedule(TimerTask task, long delay, long period)在指定 的延迟之后开始 ,重新执行 固定延迟执行的指定任务。

2.TimerTask
一个抽象类,该类实现了Runnable接口
可以由计时器进行一次性或重复执行的任务
需要重写run()方法

public class TimeTest {
    public static void main(String[] args) {
        Timer timer=new Timer();
        timer.schedule(new Task(),1000,200);    //1秒后开始,每隔200毫秒执行一次
    }
}

class Task extends TimerTask{

    @Override
    public void run() {
            for(int i=0;i<10;i++){
                System.out.println("第"+i+"次");
            }
    }
}

在这里插入图片描述
每隔两百毫秒,它会继续执行。


public class TimeTest {
    public static void main(String[] args) {
        Timer timer=new Timer();
        Calendar cal=new GregorianCalendar(2021,1,1,00,00,00);  //日历类  2021年1月1日 0时0分0秒
        timer.schedule(new Task(),cal.getTime(),200);     //2021年开始执行,每隔200毫秒开始执行
    }
}

class Task extends TimerTask{
    @Override
    public void run() {
            for(int i=0;i<10;i++){
                System.out.println("第"+i+"次");
            }
    }
}
(2)QUARTZ

任务调度框架,该框架由四大部分组成
1.Scheduler :调度器
2.Tigger:触发条件,采用DSL模式
3.JobDetall:需要处理的job
4.Job:执行逻辑
这个框架已经集成到Spring中

在官网可以下载该框架
在这里插入图片描述

打开解压包,将其lib下的文件放到项目的lib文件夹中。
然后将所有文件加入到项目的构建路径
在这里插入图片描述
在Quartz解压包下的example文件夹下的src文件夹下有官方的例子,可学习

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class HelloJob implements Job {
    public HelloJob() {
    }
    public void execute(JobExecutionContext context)
        throws JobExecutionException {
    	System.out.println("-------start---------");
        System.out.println("Hello World! - " + new Date());
        System.out.println("-------end---------");
    }
}

import static org.quartz.DateBuilder.evenSecondDateAfterNow;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

public class QuartzTest {

  public void run() throws Exception {
    // 1、创建 Scheduler的工厂
    SchedulerFactory sf = new StdSchedulerFactory();
    //2、从工厂中获取调度器
    Scheduler sched = sf.getScheduler();  
    // 3、创建JobDetail
    JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();
    // 时间
    Date runTime = evenSecondDateAfterNow();
    // 4、触发条件
    Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
   
    // 5、注册任务和触发条件
    sched.scheduleJob(job, trigger);

    // 6、启动
    sched.start();


    try {
      // 100秒后停止
      Thread.sleep(100L * 1000L);
    } catch (Exception e) {
    }
    sched.shutdown(true);
  }

  public static void main(String[] args) throws Exception {
    QuartzTest example = new QuartzTest();
    example.run();
  }
}

在这里插入图片描述
将解压包下的example的example1打开。将里面的log4j导入到src下
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第一篇 起步篇 第1章 初识Java 3 1.1 Java简介 3 1.1.1 Java的不同平台 3 1.1.2 Java发展的历程 3 1.1.3 Java的特点 4 1.2 安装开发工具包 5 1.2.1 下载JDK 5 1.2.2 安装JDK 6 1.2.3 安装后Java目录的解读 7 1.3 学会使用API 7 1.4 第一个Java程序 8 1.4.1 开发源代码 8 1.4.2 编译运行 9 1.5 小结 11 第2章 基本数据类型——构建Java 大厦的基础 12 2.1 源代码注释 12 2.1.1 单行注释 12 2.1.2 区域注释 12 2.1.3 文档注释 13 2.2 基本数据类型 14 2.2.1 整型 15 2.2.2 浮点型 17 2.2.3 char型 17 2.2.4 boolean型 18 2.3 基本数据类型值间的转换 18 2.3.1 自动转换 18 2.3.2 手动强制转换 19 2.3.3 隐含强制转换 19 2.4 标识符命名规范 20 2.4.1 正确的命名标识符 20 2.4.2 提倡的命名习惯 21 2.5 小结 21 第3章 表达式——描述行为的素 22 3.1 不简单的算术运算符 22 3.1.1 “+”运算符 22 3.1.2 “-”运算符 24 3.1.3 “*”运算符 25 3.1.4 “/”运算符 25 3.1.5 “%”运算符 26 3.2 自增自减运算 27 3.3 关系运算 28 3.3.1 等于/不等于运算 28 3.3.2 比较大小运算 29 3.4 逻辑运算 30 3.4.1 “与”运算 30 3.4.2 “或”运算 31 3.4.3 “非”运算 32 3.5 三运算符 32 3.6 位运算 33 3.7 移位运算 34 3.7.1 “”左移 35 3.7.2 “”右移 35 3.7.3 “”无符号右移 36 3.7.4 令人困扰的例子 37 3.8 赋值运算 37 3.8.1 普通赋值运算 37 3.8.2 运算赋值运算 38 3.9 括号及运算符间的优先级关系 38 3.10 常用数学工具包——java.lang.Math类 39 3.10.1 数学常量 39 3.10.2 常用数学函数 40 3.11 小结 41 第4章 流程控制——Java世界的航行舵手 42 4.1 if条件语句 42 4.1.1 简略形式 42 4.1.2 完全形式 43 4.1.3 语句的嵌套 43 4.2 switch多分支语句 45 4.2.1 基本语法 45 4.2.2 合法的判断表达式 46 4.2.3 合法的case表达式 47 4.2.4 详细执行流程 49 4.3 while循环语句 50 4.4 do-while循环语句 52 4.5 for循环语句 53 4.5.1 基本语法 53 4.5.2 声明的三大组成部分 54 4.5.3 复杂的for循环案例 55 4.5.4 用for实现其他循环 55 4.6 break中断语句 56 4.7 continue继续语句 57 4.8 小结 58 第5章 数组——以不变应万变的哲学 59 5.1 数组的声明及创建 59 5.1.1 声明数组引用 59 5.1.2 创建数组对象 60 5.2 Java中数组的实现机制 61 5.3 数组的初始化 63 5.3.1 默认初始化 63 5.3.2 利用循环初始化 64 5.3.3 枚举初始化 66 5.4 数组的相互赋值 67 5.4.1 基本类型数组赋值规则 67 5.4.2 引用型数组赋值规则 68 5.5 数组的常用操作 69 5.5.1 数组复制 69 5.5.2 数组排序 71 5.5.3 搜索指定素 72 5.5.4 比较数组中的素 73 5.6 关于args[] 73 5.7 小结 74 第二篇 基础篇 第6章 对象和类——Java世界的细胞 77 6.1 面向对象概述 77 6.1.1 面向对象程序设计思想的诞生 77 6.1.2 面向过程与面向对象思想的对比 78 6.1.3 面向对象技术的背景和特点 79 6.2 类的定义与对象的创建 80 6.3 成员变量 81 6.3.1 成员变量的开发与使用 81 6.3.2 成员变量的初始值 82 6.3.3 对象引用变量的比较 84 6.4 方法 85 6.5 变长参数 86 6.6 引用问题 87 6.6.1 调用不存在的方法或成员变量 87 6.6.2 用空引用进行调用 88 6.6.3 数组的空引用问题 89 6.7 局部变量 89 6.7.1 局部变量的作用域 89 6.7.2 局部变量的初始化 90 6.8 this预定义对象引用 92 6.9 擅用系统已有的类 94 6.9.1 Java中的Date类 94 6.9.2 Java中的GregorianCalendar类 96 6.9.3 擅用系统已有类的思想 98 6.10 小结 99 第7章 访问控制——Java世界的卫兵 100 7.1 包的使用 100 7.1.1 声明创建包 100 7.1.2 引入包内的资源 102 7.1.3 静态引入 104 7.2 类的访问控制 105 7.2.1 公有访问级别 105 7.2.2 默认访问级别 106 7.2.3 类与源代码文件的搭配 106 7.3 成员的访问控制 107 7.3.1 公共类型 107 7.3.2 私有类型 108 7.3.3 默认类型 109 7.3.4 保护类型 109 7.3.5 Java中封装的实现 110 7.4 final的变量 112 7.4.1 final的成员变量 113 7.4.2 final的局部变量 115 7.5 static关键字的使用 116 7.5.1 静态成员 116 7.5.2 静态成员的访问 117 7.5.3 静态最终成员变量 119 7.6 小结 121 第8章 继承——多态的支柱 122 8.1 继承概述 122 8.1.1 类之间的关系 122 8.1.2 面向对象中的继承性 124 8.2 类的继承 125 8.3 成员变量的继承与隐藏 126 8.3.1 成员变量的继承规则 126 8.3.2 成员变量的隐藏 129 8.4 对象引用的使用 130 8.4.1 对象引用能指向的对象类型 130 8.4.2 对象引用的强制类型转换 131 8.4.3 对象引用所能调用的成员 132 8.4.4 对象引用的赋值与比较 133 8.5 方法的继承与重写 135 8.5.1 方法的继承规则 135 8.5.2 方法重写的基本知识 136 8.5.3 构成重写的条件 137 8.5.4 返回类型的规则 138 8.5.5 访问级别的要求 140 8.5.6 重写基于继承 141 8.5.7 静态方法没有重写 141 8.5.8 通过重写扩展父类方法的功能 143 8.5.9 替代性原理 144 8.6 方法的重载 145 8.6.1 方法重载的规则 145 8.6.2 重载方法的匹配 145 8.6.3 重写与重载的区别 149 8.7 final与继承 149 8.7.1 最终的类 149 8.7.2 最终的方法 150 8.8 abstract与继承 151 8.8.1 抽象的类 151 8.8.2 抽象的方法 152 8.9 基于继承的多态 154 8.10 小结 155 第9章 接口——灵活性的基石 156 9.1 概述及其特性 156 9.2 成员变量在接口中的使用 157 9.2.1 语法规则 157 9.2.2 接口中成员变量的作用 158 9.3 方法在接口中的使用 159 9.3.1 语法规则 159 9.3.2 如何实现接口 160 9.3.3 接口引用的使用 162 9.3.4 接口中方法无法使用的修饰符 165 9.4 接口与抽象类 166 9.4.1 语法上的不同 167 9.4.2 具体含义的不同 167 9.5 基于接口的多态 169 9.6 接口与回调 170 9.7 instanceof的使用 172 9.7.1 基本语法与使用 172 9.7.2 不允许进行测试的情况 174 9.8 小结 175 第10章 构造器——对象制造的工厂 176 10.1 基础知识 176 10.1.1 编写构造器的语法规则 176 10.1.2 访问限制修饰符与构造器 176 10.1.3 构造器与返回类型 179 10.2 创建对象 180 10.3 重载构造器 181 10.4 级联调用的构造器 182 10.4.1 构造器的调用流程及默认构造器 182 10.4.2 自定义构造器需要注意的问题 185 10.4.3 不能继承构造器 186 10.4.4 调用兄弟构造器 187 10.5 单列模式 189 10.6 Java程序的加载过程 190 10.7 小结 192 第三篇 高级基础篇 第11章 异常处理——Java世界的医生 195 11.1 异常处理的基本知识 195 11.1.1 try和catch捕获异常 195 11.1.2 异常的传播过程 198 11.1.3 finally语句块的使用 199 11.1.4 try、catch及finally语句块之间需要注意的问题 201 11.2 异常的层次结构 203 11.2.1 捕获异常 203 11.2.2 未捕获异常 205 11.3 再次抛出异常 206 11.3.1 什么是异常的再抛出 206 11.3.2 显性再抛出 207 11.3.3 隐性再抛出 209 11.3.4 方法重写对抛出异常声明的约束 210 11.4 定义自己的异常 212 11.4.1 创建自己的异常类 212 11.4.2 使用自定义的异常类 213 11.4.3 显性再抛出作用的体现 215 11.5 异常的匹配 217 11.5.1 同时捕获多种异常 217 11.5.2 多个catch语句的先后顺序 218 11.6 断言 219 11.6.1 什么是断言 219 11.6.2 如何启用/关闭断言 220 11.6.3 防止滥用断言 222 11.7 小结 222 第12章 封装类——鸿沟之上的桥梁 223 12.1 封装类的基本知识 223 12.1.1 封装类概述 223 12.1.2 创建封装类对象 223 12.1.3 封装类对象的其他知识 225 12.2 数据转换功能 226 12.2.1 基本数据类型值转换为字符串 226 12.2.2 字符串转换为基本数据类型值 229 12.3 其他常用方法 231 12.3.1 静态工厂方法 231 12.3.2 isNaN方法 232 12.3.3 equals方法 233 12.4 自动打包/解包 -235 12.4.1 自动打包 235 12.4.2 自动解包 236 12.5 特殊的数值计算 237 12.5.1 特大整数的计算 237 12.5.2 浮点数的精确计算 239 12.6 小结 242 第13章 字符串——优异的内存组织机制 243 13.1 String类的基础知识 243 13.1.1 对象的创建 243 13.1.2 巧用构造器 244 13.1.3 String类的重要方法 245 13.2 String对象的内存机制 248 13.2.1 一段令人困惑的字符串程序 248 13.2.2 “一次投入,终身回报”的String内存机制 249 13.2.3 String对象特殊机制付出的代价 252 13.3 StringBuffer类 253 13.3.1 弥补String不足的StringBuffer类 253 13.3.2 编写方法链以及StringBuffer类的重要方法 255 13.4 StringBuilder类 258 13.5 正则表达式 259 13.5.1 正则表达式的基本语法 259 13.5.2 Pattern类简介 262 13.5.3 Matcher类简介 263 13.5.4 Pattern与Matcher类的综合应用 264 13.6 String类中正则式的应用 266 13.6.1 模式匹配检查 266 13.6.2 利用正则式进行查找替换 267 13.6.3 利用正则式对字符串进行分析 268 13.7 小结 269 第14章 集合框架——强大的对象管理器 270 14.1 Object类——所有类的超类 270 14.1.1 toString方法的重写 270 14.1.2 equals方法的意义 271 14.1.3 hashCode方法的意义 272 14.2 重写equals与hashCode方法 273 14.2.1 重写equals方法 273 14.2.2 重写hashCode方法 275 14.3 集合框架的层次结构 -277 14.4 Ordered与Sorted的接口 278 14.4.1 Ordered的排序 278 14.4.2 Sorted的排序 279 14.5 列表 279 14.5.1 列表接口——st 279 14.5.2 列表的数组实现 281 14.5.3 历史悠久的向量 282 14.5.4 列表的链接实现 284 14.5.5 依赖性倒置原理 285 14.5.6 将数组转换为列表 285 14.6 集合 286 14.6.1 Set接口及含义 286 14.6.2 HashSet类的使用 287 14.6.3 equals与hashCode方法重写规定的作用 288 14.6.4 LinkedHashSet类的使用 291 14.6.5 SortedSet接口与TreeSet类 292 14.6.6 自定义满足Sorted集合的类 293 14.6.7 定制SortedSet的排序规则 296 14.6.8 集合的遍历 298 14.6.9 使用for-each循环遍历集合 300 14.7 映射集 301 14.7.1 Map接口及含义 301 14.7.2 HashMap类的使用 302 14.7.3 Hashtable类的使用 303 14.7.4 LinkedHashMap类的使用 304 14.7.5 SortedMap接口与TreeMap类 305 14.7.6 映射的遍历 308 14.8 栈在Java中的实现 309 14.8.1 Stack类的使用 309 14.8.2 Deque接口的使用 310 14.8.3 利用栈计算数学表达式 311 14.9 集合素的常用操作 314 14.9.1 素排序 315 14.9.2 搜索特定素 316 14.9.3 任意打乱素顺序 317 14.9.4 其他的简单操作 318 14.10 小结 320 第15章 内部类——Java世界的多面手 321 15.1 非静态内部类 321 15.1.1 语法规则 321 15.1.2 外部类之内创建内部类对象 322 15.1.3 外部类之外创建内部类对象 323 15.1.4 内部类与外部类之间的成员互访 324 15.1.5 内部类与外部类的预定义对象引用this 327 15.2 局部内部类 328 15.2.1 局部内部类的定义及创建 328 15.2.2 局部变量与局部内部类 329 15.2.3 静态方法中的局部内部类 331 15.3 静态内部类 332 15.3.1 语法规则 332 15.3.2 创建静态内部类的对象 332 15.3.3 静态/非静态内部类的区别 333 15.4 匿名内部类 334 15.4.1 基于继承的匿名内部类 334 15.4.2 基于接口实现的匿名内部类 335 15.4.3 匿名内部类的初始化 337 15.4.4 匿名内部类作用的体现 337 15.5 理解内部类 339 15.6 内部接口 340 15.6.1 定义在类中的内部接口 340 15.6.2 定义在接口中的内部接口 341 15.7 小结 342 第16章 多线程——Java中的并发协作 343 16.1 线程的基本知识 343 16.1.1 多线程编程的意义 343 16.1.2 定义自己的线程 344 16.1.3 创建线程对象 345 16.1.4 启动线程 347 16.1.5 同时使用多个线程 348 16.2 线程的状态 350 16.3 线程的调度 351 16.3.1 睡眠 351 16.3.2 线程的优先级 353 16.3.3 线程的让步 355 16.3.4 守护线程 357 16.4 线程的同步 359 16.4.1 同步方法简介 359 16.4.2 简单使用同步方法 360 16.4.3 线程同步调度的方法 362 16.4.4 “生产者-消费者”案例的框架 362 16.4.5 “生产者-消费者”案例的实际运行 365 16.4.6 notify方法的使用 366 16.4.7 同步的语句块 367 16.4.8 线程的死锁 369 16.4.9 防止错误的使用wait、notify、notifyAll方法 371 16.5 获取当前正在运行的线程 372 16.6 volatile关键字的含义与使用 372 16.7 小结 373 第17章 高级线程开发 374 17.1 线程池的使用 374 17.1.1 线程池的基本思想 374 17.1.2 JavaSE 5.0中固定尺寸线程池的基本知识 374 17.1.3 自定义尺寸固定线程池的使用 375 17.1.4 单任务线程池的使用 377 17.1.5 可变尺寸线程池的使用 378 17.1.6 延迟线程池的使用 380 17.1.7 使用自定义参数的线程池 381 17.2 有返回值的线程调用 384 17.2.1 Callable接口简介 384 17.2.2 Future接口简介 384 17.2.3 Callable与Future接口的具体使用 385 17.3 资源的封锁 386 17.3.1 Lock接口与ReentrantLock类简介 386 17.3.2 ReentrantLock锁的具体使用 387 17.3.3 ReadWriteLock接口与ReentrantReadWriteLock类简介 390 17.3.4 ReentrantReadWriteLock读/写锁的具体使用 391 17.4 信号量的使用 393 17.4.1 Semaphore类简介 393 17.4.2 Semaphore类的具体使用 394 17.5 队列 396 17.5.1 Queue接口介绍 396 17.5.2 PriorityQueue类的知识与使用 397 17.5.3 BlockingQueue接口介绍 399 17.6 阻塞的栈操作 401 17.6.1 BlockingDeque接口与LinkedBlockingDeque类简介 401 17.6.2 LinkedBlockingDeque类的具体使用 402 17.7 线程安全的单变量操作 403 17.7.1 atomic包简介 403 17.7.2 atomic包中类的具体使用 404 17.8 障碍器 406 17.8.1 CyclicBarrier类简介 406 17.8.2 CyclicBarrier类的具体使用 407 17.9 小结 408 第18章 内存管理与垃圾收集——自动化的典范 409 18.1 什么是“垃圾” 409 18.1.1 对象成为“垃圾”的条件 409 18.1.2 符合条件的几种情况 409 18.2 垃圾收集器 411 18.2.1 垃圾收集器的基本介绍 411 18.2.2 申请垃圾收集器运行 412 18.3 垃圾收集前的处理工作 413 18.3.1 finalize方法的重写 414 18.3.2 finalize方法的安全问题 415 18.3.3 最终守护者模式 417 18.3.4 再谈非线程“垃圾” 418 18.3.5 再谈线程“垃圾” 419 18.4 3种特殊的引用 420 18.4.1 弱引用 421 18.4.2 软引用 422 18.4.3 幻影引用 424 18.5 小结 424 第四篇 Swing GUI篇 第19章 初识Swing 427 19.1 Swing概述 427 19.2 一个简单的Swing程序 428 19.3 Swing的过人之处 429 19.3.1 完全轻量级的控件 430 19.3.2 可插拔的感观风格 430 19.3.3 更多的控件扩展 430 19.4 Swing和AWT的对比 432 19.4.1 Swing与AWT之间的关系 432 19.4.2 Swing与AWT的控件的混用建议 432 19.5 小结 433 第20章 开始创建Swing应用程序 434 20.1 窗体——JFrame类 434 20.1.1 JFrame类简介 434 20.1.2 创建简单窗体 436 20.2 AWT1.1事件处理模型 438 20.2.1 事件的处理模型简介 438 20.2.2 事件的层次结构 439 20.2.3 窗体事件 440 20.2.4 事件适配器 442 20.3 面板——JPanel类 444 20.3.1 容器的基本知识 444 20.3.2 JPanel类简介 445 20.3.3 JPanel的简单使用 445 20.4 标签——JLabel类 446 20.4.1 JLabel类简介 446 20.4.2 使用JLabel类 448 20.5 按钮——JButton类 449 20.5.1 JButton类简介 449 20.5.2 动作事件 450 20.5.3 监听器与事件源对应关系的研究 451 20.6 小结 454 第21章 布局管理器——界面设计的利器 455 21.1 布局管理器设计思想概述 455 21.2 常用布局管理器简介 455 21.3 流布局 456 21.3.1 流布局简介 456 21.3.2 使用流布局 458 21.4 网格布局 459 21.4.1 网格布局简介 459 21.4.2 使用网格布局 460 21.5 边框布局 462 21.5.1 边框布局简介 462 21.5.2 使用边框布局 464 21.6 空布局 465 21.6.1 空布局简介 465 21.6.2 使用空布局 466 21.7 卡片布局 467 21.7.1 卡片布局简介 467 21.7.2 使用卡片布局 468 21.8 箱式布局 470 21.8.1 箱式布局简介 471 21.8.2 Box容器简介 472 21.8.3 Box容器与BoxLayout布局管理器的使用 473 21.9 弹簧布局 475 21.9.1 弹簧布局的基本思想 475 21.9.2 SpringLayout类简介 478 21.9.3 SpringLayoutConstraints内部类简介 479 21.9.4 Spring类简介 480 21.9.5 弹簧布局的简单使用 480 21.9.6 描述法弹簧布局的具体使用 482 21.10 小结 483 第22章 Swing常用基本控件 484 22.1 控件类概述 484 22.2 文本框与密码框 487 22.2.1 JTextField类简介 487 22.2.2 JPasswordField类简介 488 22.2.3 登录窗口的案例 489 22.3 Swing中的文本区 491 22.3.1 JTextArea类简介 491 22.3.2 JScrollPane类详细介绍 493 22.3.3 文本区与滚动窗口的组合使用 494 22.4 可以记录状态的开关按钮 496 22.4.1 JToggleButton类简介 496 22.4.2 开关按钮的使用 497 22.5 单选按钮与复选框 499 22.5.1 JRadioButton类简介 499 22.5.2 ButtonGroup类简介 500 22.5.3 JCheckBox类简介 500 22.5.4 ItemEvent事件 501 22.5.5 一个关于ItemEvent事件的例子 502 22.5.6 单选按钮与复选框的综合案例 504 22.6 小结 506 第23章 Swing常用高级控件 507 23.1 选项卡的相关知识与使用 507 23.1.1 JTabbedPane类简介 507 23.1.2 ChangeEvent事件 509 23.1.3 JTabbedPane实现选项卡的例子 509 23.2 分割窗格 511 23.2.1 JSplitPane类简介 511 23.2.2 分割窗格的嵌套使用 513 23.3 滑块与进度条 514 23.3.1 JSlider类简介 514 23.3.2 JProgressBar类简介 516 23.3.3 滑块与进度条的使用 518 23.4 格式化文本框 519 23.4.1 JFormattedTextField类简介 520 23.4.2 JFormattedTextField中的格式器 521 23.4.3 格式化文本框的具体使用 523 23.5 编辑器面板 525 23.5.1 JEditorPane类简介 525 23.5.2 HyperlinkEvent事件 527 23.5.3 使用JEditorPane实现简单的浏览器 527 23.6 列表框的知识与使用 529 23.6.1 JList类简介 530 23.6.2 ListSelectionEvent事件 531 23.6.3 控件MVC设计模式简介 532 23.6.4 JList类的使用 533 23.7 下拉列表框 534 23.7.1 JComboBox类简介 534 23.7.2 下拉列表框的具体使用 536 23.8 微调控制器 538 23.8.1 JSpinner类简介 538 23.8.2 微调控制器模型简介 539 23.8.3 微调控制器的具体使用 542 23.9 小结 543 第24章 菜单、工具栏与对话框 544 24.1 Swing中的菜单 544 24.1.1 Swing菜单的基本知识 544 24.1.2 JMenuBar类简介 545 24.1.3 JMenuItem类简介 546 24.1.4 JMenu类简介 549 24.1.5 JRadioButtonMenuItem类简介 550 24.1.6 JCheckBoxMenuItem类简介 551 24.1.7 菜单使用综合案例 551 24.2 Swing中的弹出式菜单 554 24.2.1 JPopupMenu类简介 554 24.2.2 弹出式菜单的显示 555 24.3 鼠标事件 555 24.3.1 鼠标事件简介 555 24.3.2 弹出式菜单的具体使用 557 24.4 工具栏的开发 559 24.4.1 JToolBar类简介 559 24.4.2 工具栏的具体使用 560 24.5 Swing中的对话框 562 24.5.1 JDialog类简介 562 24.5.2 JOptionPane类简介 563 24.5.3 JOptionPane对话框的具体使用 566 24.5.4 文件选择器 568 24.5.5 颜色选择器 571 24.5.6 文件、颜色对话框综合案例 572 24.6 小结 574 第25章 Swing中的树状列表 575 25.1 与树有关的专有名词 575 25.2 JTree类简介 576 25.2.1 JTree类构造器简介 576 25.2.2 JTree类的常用方法说明 577 25.2.3 一个简单JTree的实例 579 25.3 树模型 580 25.3.1 TreeModel接口简介 580 25.3.2 默认树模型DefaultTreeModel类 581 25.4 树的节点 582 25.4.1 TreeNode接口简介 582 25.4.2 MutableTreeNode接口简介 583 25.4.3 DefaultMutableTreeNode类简介 583 25.5 树的路径 586 25.6 树的相关事件 587 25.6.1 TreeSelectionEvent事件 587 25.6.2 TreeExpansionEvent事件 589 25.6.3 TreeModelEvent事件 589 25.7 树节点的绘制 590 25.7.1 TreeCellRenderer接口简介 590 25.7.2 DefaultTreeCellRenderer类简介 590 25.7.3 自定义绘制器案例 592 25.8 树状列表的综合案例 593 25.8.1 案例概述 593 25.8.2 搭建界面 594 25.8.3 添加信息提示功能 595 25.8.4 开发节点增删功能 597 25.8.5 添加图标更改功能 600 25.9 小结 602 第26章 Swing中的表格 603 26.1 初识表格 603 26.2 JTable类 604 26.2.1 JTable类简介 604 26.2.2 使用JTable的简单案例 606 26.3 表格的数据模型 608 26.3.1 TableModel接口简介 608 26.3.2 AbstractTableModel类简介 608 26.3.3 DefaultTableModel类简介 609 26.3.4 使用表格模型的简单案例 611 26.4 表格列 612 26.5 表格列模型 613 26.5.1 TableColumnModel接口简介 614 26.5.2 DefaultTableColumnModel类简介 614 26.6 表格的相关事件 616 26.6.1 表格相关事件简介 616 26.6.2 表格事件的示例程序 618 26.7 表格绘制器与编辑器 620 26.7.1 表格绘制器简介 620 26.7.2 表格编辑器简介 622 26.8 自定义表格编辑器与绘制器的综合案例 624 26.8.1 案例概述 624 26.8.2 界面框架的搭建 624 26.8.3 自定义表格模型的开发以及表格控件的添加 625 26.8.4 自定义绘制器的开发 627 26.8.5 添加自定义编辑器 628 26.9 表格中的排序与过滤 630 26.9.1 RowSorter类简介 631 26.9.2 DefaultRowSorter类简介 631 26.9.3 TableRowSorter类简介 632 26.9.4 RowFilter类简介 633 26.10 表格排序与过滤的综合案例 635 26.10.1 案例概述 635 26.10.2 搭建界面框架 636 26.10.3 添加表格 637 26.10.4 为表格添加排序器 638 26.10.5 添加设置过滤条件的控件 639 26.10.6 为表格设置过滤器 641 26.11 小结 643 第27章 高级Swing开发 644 27.1 Swing线程 644 27.1.1 事件分发线程简介 644 27.1.2 事件分发线程线程模型带来的问题 644 27.1.3 解决不当使用事件分发线程引发的问题 645 27.2 Robot类的知识与应用 648 27.2.1 Robot类简介 648 27.2.2 利用Robot类实现自动演示功能 649 27.3 Desktop类的知识与应用 652 27.3.1 Desktop类简介 652 27.3.2 使用Desktop的综合案例 654 27.4 Swing应用程序的感观 656 27.4.1 UIManager类简介 657 27.4.2 MetalLookAndFeel类简介 658 27.4.3 动态切换外观风格的案例 658 27.5 系统托盘 661 27.5.1 SystemTray类简介 661 27.5.2 TrayIcon类简介 662 27.5.3 使用系统托盘的简单案例 663 27.6 应用程序的监控与管理 665 27.7 小结 666 第五篇 图形图像篇 第28章 图形绘制与动画 669 28.1 绘制简单图形 669 28.1.1 画布的相关知识 669 28.1.2 画笔的相关知识 670 28.1.3 颜色的调配 672 28.1.4 图形绘制的简单案例 673 28.2 绘制各种文本 674 28.2.1 drawString方法简介 675 28.2.2 字体的控制 675 28.2.3 文本绘制的简单案例 676 28.3 Java 2D 677 28.3.1 Graphics 2D类简介 677 28.3.2 控制线条的粗细 679 28.3.3 使用颜渐变色 680 28.3.4 图形变换 681 28.3.5 异或模式绘图 683 28.3.6 抗锯齿 685 28.4 动画的开发 685 28.4.1 动画实现的原理 685 28.4.2 Timer类简介 686 28.4.3 简单动画的案例 687 28.5 小结 689 第29章 图像处理 690 29.1 图像的加载与绘制 690 29.1.1 Image类简介 690 29.1.2 绘制Image图像 692 29.2 图标的使用 693 29.2.1 Icon接口简介 694 29.2.2 ImageIcon类简介 695 29.3 图像的编码处理 697 29.3.1 JPEG编码器简介 697 29.3.2 GifEncoder编码器简介 698 29.4 屏幕图像抓取 700 29.4.1 createScreenCapture方法简介 700 29.4.2 抓屏功能的案例 700 29.5 图像滤镜的开发 703 29.5.1 图像的灰度处理的基本知识 703 29.5.2 图像灰度处理的案例 705 29.5.3 RGB色彩通道过滤的基本知识 707 29.5.4 RGB色彩通道过滤的案例 708 29.5.5 卷积滤镜的基本知识 711 29.5.6 卷积滤镜的案例 712 29.6 小结 714 第六篇 高级应用篇 第30章 JDBC数据库开发 717 30.1 数据库应用的两种架构模型 717 30.1.1 两层结构数据库应用的架构模型 717 30.1.2 三层结构数据库应用的架构模型 718 30.2 JDBC的层次结构 718 30.3 JDBC编程基础 719 30.3.1 创建数据库 720 30.3.2 JDBC-ODBC连接桥 721 30.3.3 加载JDBC驱动 722 30.3.4 建立数据库连接 723 30.3.5 执行SQL命令 725 30.3.6 结果集 725 30.3.7 连接数据库的简单案例 726 30.3.8 预编译语句 728 30.4 访问其他数据库 730 30.4.1 访问MySQL数据库 730 30.4.2 访问Oracle数据库 732 30.5 事务 733 30.5.1 编写事务 734 30.5.2 批处理 736 30.6 可滚动结果集 738 30.6.1 获得可滚动的结果集 738 30.6.2 可滚动与不可滚动结果集的比较 739 30.6.3 控制游标移动 739 30.7 数据 742 30.7.1 数据库数据 742 30.7.2 结果集数据 744 30.8 数据库综合案例——DBManager 746 30.8.1 案例概述 746 30.8.2 搭建界面 747 30.8.3 开发输入数据库连接信息的对话框 749 30.8.4 初始化树状列表根节点 751 30.8.5 初始化树状列表表节点 753 30.8.6 初始化树状列表列节点 754 30.8.7 添加显示表数据的功能 756 30.8.8 添加显示列信息的功能 758 30.8.9 自定义树节点图标 759 30.8.10 连接其他类型数据库 761 30.8.11 案例小结 761 30.9 小结 761 第31章 Derby数据库的应用 762 31.1 Derby数据库简介 762 31.1.1 Derby的发展史及特性概述 762 31.1.2 JavaSE 6.0中Derby的目录结构 762 31.2 管理工具ij 763 31.2.1 准备工作 763 31.2.2 简单使用 764 31.3 Derby数据库的嵌入式应用 767 31.3.1 嵌入式Derby的工作原理 767 31.3.2 嵌入式Derby应用的开发步骤 767 31.3.3 使用嵌入式Derby的简单案例 768 31.4 Derby数据库的网络模式应用 770 31.4.1 网络模式Derby的工作原理 771 31.4.2 操作网络模式的Derby 771 31.4.3 开发启动Derby网络服务的程序 772 31.4.4 使用网络模式Derby的简单案例 774 31.5 小结 775 第32章 I/O流 776 32.1 I/O流的基本原理与分类 776 32.1.1 流的概念 776 32.1.2 节点流与处理流 776 32.1.3 字节流与字符流 777 32.2 节点流 780 32.2.1 常用节点流简介 780 32.2.2 使用节点流的简单案例 782 32.3 处理流 783 32.3.1 常用处理流简介 783 32.3.2 使用处理流的简单案例 785 32.4 系统输入输出 786 32.4.1 系统输入流 787 32.4.2 系统输出流 788 32.4.3 格式化输出的简单案例 790 32.4.4 系统错误流 791 32.4.5 系统输入输出重定向 792 32.5 进程控制 793 32.5.1 Process类简介 793 32.5.2 控制进程的简单案例 794 32.6 目录文件管理 795 32.6.1 File类简介 796 32.6.2 使用File的简单案例 797 32.7 I/O流综合案例——数据库图片查看器 798 32.7.1 案例概述 798 32.7.2 界面与程序框架的搭建 799 32.7.3 添加图片功能的开发 801 32.7.4 查看图片功能的开发 804 32.7.5 删除图片功能的开发 806 32.7.6 案例小结 806 32.8 小结 806 第33章 套接字网络开发 807 33.1 TCP/IP协议简介 807 33.2 网络开发中的常用工具类 808 33.2.1 URL简介 808 33.2.2 URL类简介与使用 809 33.2.3 InetAddress类简介与使用 811 33.3 Socket编程 812 33.3.1 Socket编程简介 813 33.3.2 ServerSocket类简介 813 33.3.3 Socket类简介 814 33.3.4 C/S架构程序的简单案例 815 33.4 小结 817 第34章 反射与注解 818 34.1 反射 818 34.1.1 Class类简介 818 34.1.2 Class类的简单使用 820 34.1.3 数组与Class类 822 34.1.4 精确判断对象类型 823 34.1.5 Field类的知识与使用 824 34.1.6 Method类的知识与使用 826 34.1.7 Constructor类的知识与使用 828 34.1.8 反射与修饰符 830 34.1.9 取消访问限制 833 34.1.10 利用反射动态创建数组对象 835 34.2 程序注解 836 34.2.1 声明自己的注解 837 34.2.2 确定注解的使用目标 837 34.2.3 确定注解的使用时效 838 34.2.4 通过反射提取注解信息 839 34.2.5 标注性注解的使用 840 34.2.6 常用的系统注解 842 34.2.7 利用注解方便开发Web服务 844 34.2.8 注解与代码自动生成 845 34.3 小结 845 第35章 泛型程序设计 846 35.1 泛型简介 846 35.1.1 没有泛型的烦恼 846 35.1.2 泛型技术的好处 846 35.2 简单泛型程序的开发 847 35.2.1 泛型类或接口的声明 847 35.2.2 泛型方法的开发 849 35.2.3 类型变量的限制 850 35.3 泛型参数的继承以及通配符 851 35.3.1 泛型参数的继承问题 852 35.3.2 泛型通配符 852 35.3.3 泛型通配符使用的特殊注意 854 35.3.4 有限制的通配符 855 35.4 泛型的擦除 857 35.4.1 擦除的基本概念与规则 857 35.4.2 擦除引出的约束与局限性 858 35.5 系统提供的泛型类 859 35.6 小结 859 第36章 安全类型枚举 860 36.1 JavaSE 5.0之前的枚举 860 36.1.1 传统枚举实现方式的案例 860 36.1.2 传统实现方式带来的问题 861 36.2 JavaSE 5.0中的安全类型枚举 862 36.2.1 基本语法与简单使用 862 36.2.2 复杂的枚举类型 864 36.2.3 枚举类 866 36.3 小结 867 第37章 嵌入式脚本开发 868 37.1 基本步骤与知识 868 37.1.1 ScriptEngineManager类简介 868 37.1.2 ScriptEngineFactory接口简介 869 37.1.3 ScriptEngine接口简介 870 37.1.4 基本步骤 870 37.1.5 执行外部的脚本文件 871 37.2 其他第三方脚本引擎 872 37.2.1 引擎支持jar包的下载与安装 872 37.2.2 Ruby脚本的简单案例 873 37.2.3 Groovy脚本的简单案例 874 37.3 小结 874
### 回答1: JavaSE 包括 Java 编程语言、Java 虚拟机、Java 类库等内容。其中 Java 编程语言是一种面向对象的编程语言,Java 虚拟机是 Java 代码的运行环境,Java 类库包含了大量的 API,提供了丰富的功能和工具。 ### 回答2: JavaSE(Java Platform,Standard Edition)是一种广泛使用的Java平台,它包括以下要内容: 1. Java语言:JavaSE提供了完整的Java编程语言规范,包括数据类型、对象、流程控制、异常处理等。 2. 核心类库:JavaSE提供了丰富的类库,包括处理输入输出、网络通信、图形用户界面、数据库访问等功能。 3. 面向对象:JavaSE秉承了面向对象的编程理念,提供了类、对象、继承、封装等特性,方便开发者进行面向对象的开发。 4. 线程并发JavaSE提供了线程并发相关的API,使得开发者能够方便地编写支持多线程的应用程序。 5. 安全性:JavaSE具备强大的安全性能,包括沙箱安全模型、类加载机制、访问控制等,可以有效地防止恶意代码的攻击。 6. 跨平台性:JavaSE的应用程序可以在不同操作系统上运行,只需要安装对应的Java虚拟机(JVM)。 总的来说,JavaSE是Java编程的基础,提供了强大的语言特性和丰富的类库,使得开发者能够轻松地构建安全、可靠、跨平台的应用程序。 ### 回答3: JavaSE(Java Standard Edition)是Java平台的基础版,包括了Java语言的核心部分以及一些与原生平台相关的功能。 具体而言,JavaSE包括以下内容: 1. Java语言核心:JavaSE提供了完整的Java编程语言的核心特性,包括基本数据类型、控制流语句、面向对象编程、异常处理、多线程等。 2. Java类库:JavaSE包含了一系列的Java类库,提供了丰富的API(Application Programming Interface)供开发者使用,包括字符串处理、集合框架、输入输出、网络通信、GUI图形用户界面等。 3. Java虚拟机(JVM):JavaSE定义了Java虚拟机(JVM),它是一个可以在不同平台上运行Java字节码的软件,保证了Java程序的跨平台性。 4. 开发工具:JavaSE提供了一系列的开发工具,包括编译器(javac)、调试器(jdb)、构建工具(Ant、Maven)、集成开发环境(IDE)等,方便开发者进行Java程序的编写、调试和构建。 5. 安全机制:JavaSE提供了严格的安全机制,包括安全管理器、类加载器、签名和加密机制等,保护Java程序的安全性和运行环境的安全。 6. 垃圾回收机制:JavaSE通过自动垃圾回收机制,提供了方便的内存管理,避免了手动内存管理的复杂性。 7. 网络编程:JavaSE提供了一系列的网络编程相关的类库,方便开发者进行网络通信的编程。 总之,JavaSE是Java开发的基础版,提供了Java语言核心特性、类库、虚拟机、开发工具、安全机制等,为开发者提供了一个全面的Java开发环境。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值