高级Java编程
- 1 多线程
- 1.1 XX
- 1.2 程序
- 1.3 进程
- 1.4 线程
- 1.4.1 多线程优点
- ★1.4.2 线程的实现
- ★1.4.3 线程的生命周期
- 1.4.4 线程的实现方式
- 1.4.4.1 继承Thread
- 1.4.4.2 实现Runnable接口
- 1.4.4.3 Runnable名下Lambda拉姆达表达式
- 1.4.4.4 Callable结束
- 1.4.4.5 例子:使用多线程完成三个图片的下载
- 1.4.4.6 扩展:Runnable和Thread的区别和联系
- 1.4.5 线程安全
- 1.4.5.1 普通方法/使用线程(洗杯子)——执行时间比较
- 1.4.5.2 线程安全问题——影响
- 1.4.5.3 线程数据共享(多窗口卖票)
- 1.4.6 线程同步和通信
- 1.4.6.1 正常通信wait & notify
- 1.4.6.2 Synchronized 同步实例方法,同步代码块
- 1.4.7 Thread.currentThread中类的方法
- 1.4.7.1 currentThread中几种常用方法
- 1.4.7.2 setPriority设置优先级
- 1.4.7.3 getState获取线程状态
- 1.4.8 JOIN
- 1.4.9 线程池
- 1.4.9.1 死锁
- 1.4.10 线程分类
- 1.4.10.1 用户线程User Thread
- 1.4.10.2 守护线程 Daemon Thread
- 1.4.11 线程阻塞&激活
- 附:yeild和sleep的区别
- 附:Lock接口和Synchronized区别
- ★附:线程和进程的区别
- ★ 存在某一张票被销售了多次
- max.1 任务调度以后说
- 2 网络编程
- 2.1 协议
- 2.2 TCP与UDP差异
- 例子:单次传输对象
- 一对多聊天室
- UDP
- URL
- 实例方法
- 例子: 图片下载
- 扩展:NIO
- 图
- 3 高级
- 3.1 常用设计模式
- 3.1.1 设计模式的分类
- 3.1.2设计模式的六大原则:
- 3.1.3 单例模式(Singleton)
- 3.1.1.1 懒汉模式
- 3.1.1.2 饿汉模式
- 3.1.1.3 懒汉模式实现线程安全的两种方法
- 3.1.4 工厂模式
- 3.1.5 观察者模式
- 3.1 反射
- 3.2 注解
- 3.3 设计模式
1 多线程
1.1 XX
1.2 程序
是计算机指令的集合,程序是一组静态的指令集,不占用系统运行资源,不能被系统调度,也不能作为独立运行的单位,以文件的形式存储在磁盘上
操作系统的发展使得多个程序能够同时运行 ,程序在各自的进程中运行,相互分离,各自独立运行,由操作系统来分配资源,如内存、安全证书
1.3 进程
- 是一个程序在其自身的地址空间中的一次执行活动(也就是调用进程),如打开记事本
- 程序不能申请系统资源,一个程序可以对应多个进程(如QQ多开)
- 是资源申请、调度和独立运行的 基本单位,使用系统中的运行资源;有独立的内存空间和系统资源
1.4 线程
进程中执行运算的最小单位,处理器分配给线程是正在处理器上运行的是线程
注意start()方法是使线程开始执行,java中的虚拟机调用线程的run()方法
多任务(同时执行多个任务)
顺序编程模型:
- 顺序编程模型是自然的、常规的,顺序进行
- 编程语言中,真实世界中的每一个动作,都会抽象成一个规则的动作序列
1.4.1 多线程优点
- 可以更好的实现并行
- 恰当使用线程可以降低开发和维护的开销,并且能够提供复杂应用的性能
- CPU在线程之间开关时资源消耗比进程少很多(创建和撤销线程的开销比进程要少)。开关线程都在同一地址空间内,只需要修改线程控制表或队列,不涉及地址空间和其他工作
- java在语言级提供了对多线程程序设计的支持
注:如果没有多线程的支持,会对如垃圾回收机制特效产生影响,会和所有的业务操作挂钩 - 增加程序的执行效率,程序切换执行,时间比较短(但对于CPU来说,某个时刻只有一个线程在执行)
★1.4.2 线程的实现
Thead
java.lang.Thread
使用线程步骤:
定义线程(线程开始)->创建线程对象->启动线程->终止线程(一般让他自己结束看1.4.4)
每个独立的线程都是Thread类的一个对象
线程中独立于其他线程所执行的指令代码由Thread类的run方法提供
多线程协作运行->创建多个独立的Thread类对象->线程与线程之间所执行的指令代码会存在差异,所以实现独立执行不同指令代码的多线程引用程序最简单直接方法如下
实现步骤:
- 继承Thread类
- 重写run方法
- 构建其对象
例子:体会多线程协作运行
public class TestXC {
public static void main(String[] args) {
//第三步:并构建其对象
A a = new A();
B b = new B();
//注意!!!:启动线程start方法处于"就绪状态(等待CPU分配资源)",并没有马上执行
//如果直接调用run方法,只是普通调用!
a.start();
b.start();
}
}
//第一步:继承Thread类
class A extends Thread{
//第二步:重写run方法
@Override
public void run() {
while (true) {
System.out.println("我作妖");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//同理,不写
class B extends Thread{
@Override
public void run() {
while (true) {
System.out.println("我作鬼");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
★1.4.3 线程的生命周期
新线程 就绪状态 运行状态 等待/堵塞 死亡状态
java中线程状态转换
描述 | |
新线程 | 当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在, JVM没有为其分配CPU时间片和其他线程运行资源 (Thread t = new Thread) |
就绪状态 | 在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会 (t.start()) |
运行状态 | 就绪态的线程获得cpu就进入运行态,执行就是run [call ]方法中的代码段 |
等待/阻塞 | 线程运行过程中被剥夺资源或者,等待某些事件就进入等待/阻塞状态, suspend()方法被调用 , sleep()方法被调用,线程使用wait()来等待条件变量;线程处于I/O等待等,调用suspend方法将线程的状态转换为挂起状态。这时,线程将释放占用的所有资源,但是并不释放锁,所以容易引发死锁,直至应用程序调用resume方法恢复线程运行。等待事件结束或者得到足够的资源就进入就绪态 |
死亡状态 | 当线程体运行结束[正常结束],由JVM收回线程占用的资源 |
1.4.4 线程的实现方式
建议结束的方法不用stop(已弃用),所以采用以下三种方法
注:start调用本地方法start0调用run,才有线程,否则是普通方法
1.4.4.1 继承Thread
public class TestXC {
public static void main(String[] args) {
//第三步:并构建其对象
A a = new A();
B b = new B();
//注意:启动线程start方法处于"就绪状态(等待CPU分配资源)",并没有马上执行
a.start();
b.start();
}
}
//第一步:继承Thread类
class A extends Thread{
//第二步:重写run方法
@Override
public void run() {
while (true) {
System.out.println("我作妖");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class B extends Thread{
@Override
public void run() {
while (true) {
System.out.println("我作鬼");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
1.4.4.2 实现Runnable接口
- Runnable存在局限性,java是单亲继承体系,一旦类继承Thread之后也没有父类可以继承的位置了,因为没有start方法,需要调用方法实现
实现步骤:
- 实现Runnable接口
- 重写Run方法
- 构造对象传入参数
public class TestRunnableMain {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建TestRunnable对象
TestRunnable tr = new TestRunnable();
//创建Thread对象,将TestRunnable对象作为参数tr传递给Thread
Thread th = new Thread(tr);
//启动程序
th.start();
System.out.println(Thread.currentThread().getId());
}
}
class TestRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("hello"+Thread.currentThread().getId());
}
}
}
1.4.4.3 Runnable名下Lambda拉姆达表达式
注意:要求自定义的接口只能有一个抽象方法需要实现,否则报错()
public class TestLambda {
/*
* 独立的线程:主线程
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Runnable r1 = () -> {
System.out.println("Lambda+匿名内部类1111");
};
// Runnalble r = () -> System.out.println("匿名内部类");
Runnable r2 = () -> {
System.out.println("Lambda+匿名内部类2222");
};
// 匿名对象
new Thread(r1).start();
new Thread(r2).start();
}
}
谁先抢到就是谁先执行
1.4.4.4 Callable结束
看下Callable接口源码
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
- Callable -> 支持泛型
- 有返回值
- @FunctionalInterface 支持拉姆达写法
实现步骤:
- 实现Callable接口,
- 实现call方法(注意有返回值)
- 创建Callable对象、FutureTask对象,将Callable作为参数传入到FutureTask
- 调用,将FutureTask对象作为参数传入Thread
例子 :使用Callable输出0-9的随机数
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestCallable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//3.创建Callable对象、FutureTask对象,将Callable作为参数传入到FutureTask
Callable<String> mycall = new MyCall();
FutureTask<String> ft = new FutureTask<String>(mycall);
//4.调用,将FutureTask对象作为参数传入Thread
Thread th = new Thread(ft);
//启动
th.start();
String result = ft.get();
System.out.println("result:"+result);
}
}
//1.实现Callable<T>接口
class MyCall implements Callable<String>{
@Override
//2.重写call方法
public String call() throws Exception {
// TODO Auto-generated method stub
System.out.println("call.");
return "call:"+(int)(Math.random()*10);
}
}
1.4.4.5 例子:使用多线程完成三个图片的下载
本例子使用第一种方法: 继承Thread
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.commons.io.FileUtils;
/**
* 使用多线程完成三个图片的下载 1.得到url地址 2.通过io相关代码实现流传递 3.启动线程,实现下载
*
* @author administrator
*
*/
public class TestPictureDownload {
public static void main(String[] args) {
// 构造三个对象 DownloadThread
DownloadThread dt1 = new DownloadThread(
"https://www.maserati.com/content/dam/maserati/international/Models/mc20/introduction/01-desktop.jpg",
"D:\\ccp\\x作业01.jpg");
DownloadThread dt2 = new DownloadThread(
"https://www.maserati.com/content/dam/maserati/international/Models/mc20/introduction/02-desktop.jpg",
"D:\\ccp\\x作业02.jpg");
DownloadThread dt3 = new DownloadThread(
"https://www.maserati.com/content/dam/maserati/international/Models/mc20/introduction/03-desktop.jpg",
"D:\\ccp\\x作业03.jpg");
// 分别启动线程,使线程处于 就绪状态
new Thread(dt1).start();
new Thread(dt2).start();
new Thread(dt3).start();
}
}
//继承Thread
class DownloadThread extends Thread {
private String url;// 要下载的源网址链接地址
private String destname;// 目标文件的文件名
/**
* 构造,提供了两个参数
*
* @param url 源网址链接地址
* @param destname目标文件的文件名
*/
public DownloadThread(String url, String destname) {
super();
this.url = url;
this.destname = destname;
}
@Override
public void run() {
// 1- 从某一个网址上获取一个链接,将这个链接文件使用流形式输出到本地 : 借助FileUtil ->外部jar commons-io
// 2- URL
File destination = new File(destname);
// 两个参数 URL 地址 File对象
try {
FileUtils.copyURLToFile(new URL(url), destination);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(destname + "下载完成");
}
}
1.4.4.6 扩展:Runnable和Thread的区别和联系
(区别) | Runnable | Thread |
类别 | 接口 | 类 |
源头 | 支持多继承的写法,定义一个类实现接口的同时还可以继承其他的类 | 实现了Runnable接口,重写了run方法 |
线程池 | (只)能放入实现Runnable 类线程 | 不能直接放入继承Thread的类[后续提到] |
优点 | Runnable适合多个相同的程序代码的线程去处理同一个资源,避免Java中的单继承的限制,增加程序的健壮性,代码可以被多个线程共享,代码和数据独立 |
1.7.4 Runnable和Thread的区别和联系?
- Thread是一个类,Runnable是一个接口;
- Thread类实现了Runnable接口,重写了run方法.
- Runnable是一个接口,定义一个类实现接口的同时还可以继承其他的类 ; Runnable 支持多继承的写法;
- Runnable适合多个相同的程序代码的线程去处理同一个资源,避免Java中的单继承的限制,增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。线程池只能放入实现Runnable 类线程,不能直接放入继承Thread的类.[后续提到]
1.4.5 线程安全
1.4.5.1 普通方法/使用线程(洗杯子)——执行时间比较
- 两个步骤 : 烧水(10s)->洗杯子(5个杯子 2s) (按照一定的步骤顺序执行) 20s (使用普通方法,直接调用run方法)
public class TestMakeTea {
public static void main(String[] args) {
BoilWater bw = new BoilWater();
WashCup wc = new WashCup();
bw.run();
wc.run();
}
}
class BoilWater{
public void run() {
System.out.println("开始烧水!");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("水烧好了");
}
}
class WashCup{
public void run() {
System.out.println("开始洗杯子!");
for (int i = 1; i <= 5; i++) {
System.out.println("第" + i + "杯子洗刷好了");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
- 两个步骤 : 烧水(10s) 洗杯子(5个杯子 2s) (两个线程,并行执行) <20s (使用线程同步,调用start方法开启线程同步)
public class TestMakeTea {
public static void main(String[] args) {
BoilWater bw = new BoilWater();
WashCup wc = new WashCup();
bw.start();
wc.start();
}
}
class BoilWater extends Thread {
@Override
public void run() {
System.out.println("开始烧水!");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("水烧好了");
}
}
class WashCup extends Thread{
@Override
public void run() {
System.out.println("开始洗杯子!");
for (int i = 1; i <= 5; i++) {
System.out.println("第"+i+"杯子洗刷好了");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
1.4.5.2 线程安全问题——影响
多个线程操作同一个对象,有可能导致线程不安全-数据丢失
体会线程安全不安全的影响——影响数据的存取
java.util.concurrent.CopyOnWriteArrayList
注:java.util.concurrent又叫juc,里面很多工具类都是线程安全的
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestCopyonWriteArrayList {
public static void main(String[] args) throws InterruptedException {
List<String> arraylist = new ArrayList<String>();
List<String> copyonwriterarraylist = new CopyOnWriteArrayList<String>();
for (int i = 1; i <=50000; i++) {
new Thread(() -> arraylist.add(Thread.currentThread().getName())).start();
new Thread(() -> copyonwriterarraylist.add(Thread.currentThread().getName())).start();
}
Thread.sleep(1000);
System.out.println("线程不安全arraylist:"+arraylist.size());
System.out.println("线程安全copyonwriterarraylist:"+copyonwriterarraylist.size());
}
}
结果解析:程序执行后,进入就绪状态的线程发现0位置没有数据,出现抢夺资源的情况,所以同时添加数据,导致数据重复
1.4.5.3 线程数据共享(多窗口卖票)
模拟售票: 很多个售票点; 用的是同一份数据; 数据的一致性,也就是每个售票点理解为一个线程操作的话,这些售票点对票的数量应该是”共享”的;
package TestXianCheng;
public class TestTicket {
public static void main(String[] args) {
TickerThread tt = new TickerThread();
//售票点1
Thread t1 = new Thread(tt);
//售票点2
Thread t2 = new Thread(tt);
//售票点3
Thread t3 = new Thread(tt);
t1.start();
t2.start();
t3.start();
}
}
class TickerThread implements Runnable {
private int ticketCount = 1000;
//线程异步
private synchronized void sale() {
//判断
if (ticketCount > 0) {
//开卖
System.out.println(Thread.currentThread().getName()+"正在卖票,还有" + --ticketCount + "张");
try {
Thread.currentThread().sleep((int)(Math.random()*10));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@Override
public void run() {
// TODO Auto-generated method stub
while (ticketCount > 0) {
sale();
}
}
}
1.4.6 线程同步和通信
- wait方法处于Object类下
- 使当前线程等待,直到另一线程调用对象的notify()/notifyAll()方法
1.4.6.1 正常通信wait & notify
package TestXianCheng;
/**
* person实体类
* @author administrator
*
*/
public class Person {
private String name;
private String sex;
//增加一个布尔类型的变量,表示的是 是否已经设置过了person对象的属性信息
// 标识flag的值:false没有设置name和sex,true有
boolean flag = false;
//获取name和sex并做输出
public synchronized void showPerson(){
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果为true才有机会执行以下代码!
System.out.println("name:"+name + "\tsex:"+sex+"\n");
//显示完了,把flag调成已设置false
flag = false;
//唤醒设置程序(setPerson)继续设置
notifyAll();
}
//设置name和sex的值
public synchronized void setPerson(String name,String sex) {
if (flag) {
// 不需要再设置了,那就等待
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep((int)(Math.random()*2000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.name = name;
this.sex = sex;
//设置完了,把flag调回已设置的状态true
flag = true;
//唤醒显示程序(showPerson)
notifyAll();
}
}
package TestXianCheng;
/**
* 设置Person信息类
* @author administrator
*
*/
public class ModifyPerson implements Runnable {
private Person p;
private int n = 0;
public ModifyPerson(Person p) {
super();
this.p = p;
}
public void modify() {
//交替设置Person
if (n % 2 == 0) {
p.setPerson("大头儿子", "男");
}else {
p.setPerson("小头阿姨", "女");
}
n++;
}
public void run() {
//死循环执行
while(true) {
modify();
}
}
}
package TestXianCheng;
/**
* 显示Person信息类
* @author administrator
*
*/
public class ShowPerson implements Runnable{
private Person p;
private int n = 0;
public ShowPerson(Person p) {
super();
this.p = p;
}
public void show() {
p.showPerson();
}
public void run() {
//死循环执行
while(true) {
show();
}
}
}
package TestXianCheng;
/**
* Person测试类
* @author administrator
*
*/
public class TestPerson {
public static void main(String[] args) {
Person p = new Person();
ModifyPerson mp = new ModifyPerson(p);
ShowPerson sp = new ShowPerson(p);
new Thread(mp).start();
new Thread(sp).start();
}
}
1.4.6.2 Synchronized 同步实例方法,同步代码块
1.4.7 Thread.currentThread中类的方法
- Thread.currentThread()得到当前线程
- Thread里面很多都是线程的方法
- currentThread()是静态方法
- 作用:得到当前正在执行的线程对象的引用
//得到的是主程序的对象引用
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
}
1.4.7.1 currentThread中几种常用方法
方法名 | 返回值类型 | 描述 |
getId() | long | 返回线程的标识符 |
getName() | String | 返回线程的名称 |
getPriority() | int | 返回线程的优先级 |
getState() | Thread.State | 返回线程的状态 |
sleep |
@Override
public void run() {
for (int i = 1; i <= 1; i++) {
System.out.println("getId:" + Thread.currentThread().getId() + "\ngetName:"
+ Thread.currentThread().getName() + "\ngetPriority:" + Thread.currentThread().getPriority()
+ "\ngetState:" + Thread.currentThread().getState());
}
}
1.4.7.2 setPriority设置优先级
- 只是说优先级高的分配到的资源会多些,优先执行而已
- 如下图,优先级不能超出1-10的取值范围,且线程的默认优先级是5
- 建议使用系统自带的优先级常量Thread.(MAX_PRIORITY/MIN_PRIORITY/NORM_PRIORITY)否则抛出IllegalArgumentException
- 如果该线程已经属于一个线程组(ThreadGroup),该线程的优先级不能超过该线程组的优先级
1.4.7.3 getState获取线程状态
状态 | 描述 |
New | 还没启动的线程 |
Runnable | java虚拟机中正在执行的线程 |
Blocked | 被阻塞等待监视器锁定的线程 |
Wating | 等待另一线程执行特定动作的线程 |
Timedwaiting | 等待另一线程执行动作达到指定等待时间的线程 |
Teminated | 已退出的线程 |
1.4.8 JOIN
- 可以理解成插队
- 插队后必须等这个线程结束,主线程才继续执行
1.4.9 线程池
解决了线程的生命周期的开销和资源不足问题
通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快
返回值 | 作用 | |
isShutdown() | boolean | 如果任务已关闭返回true |
shutdown() | void | 启动有序关闭,之前提交的任务被执行,不接受新任务 |
submit(Runnable task) | Future<?> | 提交一个可运行的任务执行,返回一个表示该任务的未来 |
优点
- 降低资源消耗
- 提高响应速度
- 无需反复创建线程
创建线程池对象java.utl.cocurrent.ExecutorService
方法 | 作用 |
newCacheThreadPool() | 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程 |
newCachedThreadPool(ThreadFactory threadFactory) | 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程,并在需要时使用提供的ThreadFactory创建新线程 |
newFixedThreadPool(int nThreads) | 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程 |
newFixedThreadPool(int nThreads,ThreadFactory threadFactory) | 创建一个线程池,重用固定数量的线程,从共享无界队列中运行,使用提供的ThreadFactory在需要时创建新线程 |
1.4.9.1 死锁
定义:某一线程在等待另一线程,而另一线程再等待其他线程,如此反复,直到链条上的线程又在等待第一个线程释放锁,这样的线程之间相互等待的连续循环,没有线程能继续的称为“死锁”
死锁的形成是潜在的,不易发现的
例子: 抢椅子
public class TestMain {
public static Object obj1 = new Object();
public static Object obj2 = new Object();
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread01 th01 = new Thread01();
Thread02 th02 = new Thread02();
th01.start();
th02.start();
}
}
class Thread01 extends Thread{
@Override
public void run() {
synchronized(TestMain.obj1) {
System.out.println("Thread01中lock obj1");
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(TestMain.obj2) {
System.out.println("Thread01中的lock obj2");
}
}
}
class Thread02 extends Thread{
@Override
public void run() {
synchronized(TestMain.obj2) {
System.out.println("Thread02中lock obj1");
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(TestMain.obj1) {
System.out.println("Thread01中的lock obj1");
}
}
}
1.4.10 线程分类
1.4.10.1 用户线程User Thread
1.4.10.2 守护线程 Daemon Thread
- 偷偷做的,隐藏的线程
- 可以通过调用Thread.setDaemon(true)实现线程 -> 守护线程
- 默认创建的线程对象是非守护的用户线程对象
守护线程 | 用户线程 | |
定义 | 指在程序运行的时候在后台提供一种通用服务的线程 | |
虚拟机的退出 | 有需要守护的线程就会一直在,如果没有了也就没有存在的必要 | |
使用守护线程需要注意的几点: |
- 当所有非守护线程终止时,程序才算真的终止了,而且会同时杀死所有守护线程
- thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常,不能把正在运行的常规线程设置为守护线程
- 在Daemon线程中产生的新线程也是Daemon的
- 不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了
- 可以通过线程对象的isDaemon()方法判定该线程是否守护线程
1.4.11 线程阻塞&激活
sleep方法
Thread.sleep()
例子:模拟线程阻塞(当i=5时,线程进入阻塞状态)
public class TestCurrentThreadMethods {
public static void main(String[] args) {
ThreadSleep ts = new ThreadSleep();
new Thread(ts).start();
}
}
class ThreadSleep implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
if (i == 5) {
try {
System.out.println("等我几秒种");
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
附:yeild和sleep的区别
(区别) | yeild | sleep |
作用 | 使当前线程重新回到就绪可执行状态 | 使当前线程进入阻塞状态 |
阻塞时长 | 不需要提供(使线程直接进入就绪状态,没有阻塞时长) | 需要提供 |
结果 | 可以使优先级高的线程得到执行的机会,而且只能使相同或更高优先级的线程有执行的机会,甚至于某些时候JVM认为不符合最优资源调度的情况下会忽略该方法的调用(类似于System.gc()) | 因为不考虑线程的优先级, 可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会 |
共同点 | 不会释放锁资源 | 不会释放锁资源 |
注: |
- 执行sleep()的线程在指定的时间内肯定不会执行
- 不会释放锁资源: 即如果正在运行的线程占有某个资源的同步锁,它不会释放掉这个同步锁,其他线程仍然不能访问该资源
- Java并不保证线程在阻塞给定的时间后能够马上执行(事实上这几乎是不可能的事情),在阻塞时间到了之后,线程进入就绪状态,继续执行的时机取决于Java虚拟机的线程调度机制,唯一能够确定的是,线程中断执行的时间是大于等于给定的阻塞时长的,因此不要将sleep用作精确度要求非常高的定时任务调度
附:Lock接口和Synchronized区别
区别 | |
Lock接口和Synchronized | Lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞 |
Lock接口提供lock和unlock方法获得和释放锁 | |
Lock接口具有比Synchronized更广泛的锁定操作 |
★附:线程和进程的区别
线程 | 进程 | |
进程中执行运算的最小单位 | 系统运行程序的基本单位 | |
处理器分配给线程的,真正在处理器上运行的是线程 | 有独立的内存空间和系统资源 |
★ 存在某一张票被销售了多次
class SaleThread implements Runnable {
/**
* 使用静态成员变量作为100张票的保存变量,是一个共享资源。
*/
private static int tickets = 100;
@Override
public void run() {
// 完成售票过程
while (true) {
/*
字符串可以作为锁对象,因为双引号包含的字符串不管在代码中如何运行,有且只有一个
*/
synchronized ("锁") {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printstacktrace();
}
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售出了" + tickets + "张票");
tickets--;
} else {
System.out.println(Thread.currentThread().getName() + "售罄!!!");
break;
}
}
}
}
}
public class Demo {
public static void main(String[] args) {
Thread t1 = new Thread(new SaleThread(),"售票人员1");
Thread t2 = new Thread(new SaleThread(),"售票人员2");
Thread t3 = new Thread(new SaleThread(),"售票人员3");
t1.start();
t2.start();
t3.start();
}
}
max.1 任务调度以后说
2 网络编程
定义:简单来说,就是实现计算机节点之间的数据通信
2.1 协议
内容 | 特点 | |
TCP(传输控制协议) | 是一种面向连接、可靠的、基于字节流的传输层通信协议 | 可靠 |
UDP(用户数据报协议) | 用于处理数据包,是一种无连接的、不可靠的通信协议 | 不可靠(不作确认只作发送) |
IP(网络协议) | 把数据从源传送到目的地,它不负责保证传送可靠性、流控制、包顺序等 | 不可靠(不保证传送可靠性) |
ICMP(网络控制报文协议) | TCP/IP协议栈的一个子协议,用于在IP主机、路由器之间传递控制消息 |
2.2 TCP与UDP差异
例子:单次传输对象
MyClientSocket类
package com.ccp.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.Socket;
public class MyClientSocket {
public static void main(String[] args) {
// TODO Auto-generated method stub
InputStream is = null;
ObjectInputStream ois = null;
Socket client = null;
superStar ss = new superStar();
try {
// client = new Socket("192.168.12.27", 9791);
client = new Socket("127.0.0.1", 2000);
// 写给服务器
is = client.getInputStream();
ois = new ObjectInputStream(is);
// 是Obeject才转型
if (is instanceof Object) {
try {
ss = (superStar) ois.readObject();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("client:"+ss);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
//释放资源
try {
if (ois != null) {
ois.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if (client != null) {
client.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
MyServerSocket类
package com.ccp.socket;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MyServerSocket {
public static void main(String[] args) {
// TODO Auto-generated method stub
ServerSocket serverSocket = null;
Socket server = null;
ObjectOutputStream oos = null;
OutputStream os = null;
try {
// 创建serversocket对象
serverSocket = new ServerSocket(2000);
System.out.println("开启服务监听");
// accept
server = serverSocket.accept();
// 通过socket进行流的操作
os = server.getOutputStream();
//写
oos = new ObjectOutputStream(os);
//写对象
superStar ss = new superStar("成龙", 25);
oos.writeObject(ss);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 释放资源
try {
if (oos != null) {
oos.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if (os != null) {
os.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if (server != null) {
server.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
实体类superStar
package com.ccp.socket;
import java.io.Serializable;
public class superStar implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String name;
private int age;
public superStar() {
// TODO Auto-generated constructor stub
}
public superStar(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "superStar [name=" + name + ", age=" + age + "]";
}
}
一对多聊天室
Mysever
在这里插入代码片
UDP
DatagramSocket
此类表示用来发送和接收数据报包的套接字
构造方法 | 作用 |
DatagramSocket | 构造数据报套接字并将其绑定到本地主机上任何可用的端口 |
DatagramSocket(DatagramSocketImpl impl) | 创建带有指定 DatagramSocketImpl 的未绑定数据报套接字 |
DatagramSocket(int port) | 创建数据报套接字并将其绑定到本地主机上的指定端口 |
DatagramSocket(int port, InetAddress laddr) | 创建数据报套接字,将其绑定到指定的本地地址。 |
实例方法 | 作用 | 返回值 |
receive(DatagramPacket datagramPacket) | 从此套接字接收数据报包 | void |
send(DatagramPacket datagramPacket) | 从此套接字发送数据报包 | void |
例子: 利用Datagram发送和接收UDP数据报
- DatagramPacket数据包不需要close()关闭
在这里插入代码片
URL
java.lang.Object
java.net.URL
类,Class URL表示统一资源定位符,指向万维网上的“资源”的指针
实例方法
方法 | 作用 |
openConnection | 返回connection连接 |
getHost | 主机名 |
getPort | 端口 |
getProtocol | 协议 |
getContent | 内容 |
getInputStream | 返回从此打开的连接读取的输入流 |
getOutputStream | 返回写入此连接的输出流 |
例子: 图片下载
package com.ccp.socket;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
public class URLTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
URLConnection conn = null;
InputStream is = null;
FileOutputStream fos = null;
URL url = null;
try {
// 图片地址的对象
url = new URL(
"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3429518642,4089250331&fm=26&gp=0.jpg");
System.out.println("主机名:" + url.getHost());
System.out.println("端口:" + url.getPort());
System.out.println("协议:" + url.getProtocol());
System.out.println("内容:" + url.getContent());
// 返回一个连接对象
conn = url.openConnection();
// 得到读取的输入流
is = conn.getInputStream();
// 下载地址
fos = new FileOutputStream("picture.jpg");
//字节数组
byte[] buf = new byte[1024];
int length = 0;
while ((length = is.read(buf)) != -1) {
fos.write(buf, 0, length);
}
System.out.println("下载成功");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
扩展:NIO
图
3 高级
3.1 常用设计模式
一套代码的设计经验的总结。巧妙运用可以解决很多问题。
3.1.1 设计模式的分类
模式名 | 包含 |
创建型模式 | 工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式 |
结构型模式 | 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式 |
行为型模式 | 策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式 |
3.1.2设计模式的六大原则:
- 开闭原则:支持程序扩展,但不能修改原来的代码
- 里氏代换原则
- 依赖倒转原则
- 接口隔离原则
- 迪米特法则(最少知道法则)
- 合成复用原则
3.1.3 单例模式(Singleton)
- 最简单的设计模式之一,提供了一种创建对象的最佳方式
- 创建型模式
确保在一个应用程序中某个类只有一个实例
- 单例类只能有一个实例
- 必须自己创建自己的唯一实例
- 必须给所有其他对象提供这一实例
Java中实现单例模式可以通过两种形式实现
共有点:
- 构造方法私有化
- 提供一个静态的方法返回一个类对象
3.1.1.1 懒汉模式
类加载时 不初始化
声明但不赋值 - 加入线程安全
//懒汉模式
class LazySingleton {
// 类加载,执行一次
private static LazySingleton singleton = null;
// 不能让外部直接调用构造方法,不让他直接实例化
private LazySingleton() {
// TODO Auto-generated constructor stub
}
// 加个synchronized
public static synchronized LazySingleton getInstance() {
// 判断
// 多个线程同时访问可能会产生多个实例,甚至破坏实例,违背单例设计的单例的设计原则
if (singleton == null) {
singleton = new HungarySingleton();
}
return singleton;
}
}
3.1.1.2 饿汉模式
类加载时 初始化,所以类加载比较 慢,但是获取对象更快
//饿汉模式
class HungarySingleton {
// 类加载,执行一次
private static HungarySingleton singleton = new HungarySingleton();
// 不能让外部直接调用构造方法,不让他直接实例化
private HungarySingleton() {
// TODO Auto-generated constructor stub
}
public static HungarySingleton getInstance() {
return singleton;
}
}
3.1.1.3 懒汉模式实现线程安全的两种方法
- synchornized
- 内部类
3.1.4 工厂模式
3.1.5 观察者模式
3.1 反射
3.2 注解
3.3 设计模式